├── .gitignore ├── .travis.yml ├── README.md ├── config.yaml ├── location ├── api │ └── location.go ├── client │ └── location_client.go ├── location_resource.go ├── location_service.go ├── location_service_configuration.go ├── location_service_test.go └── support_test.go ├── main.go ├── openweather ├── api │ └── conditions.go ├── client │ ├── weather_client.go │ └── weather_client_test.go └── readme.go └── test.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | weather-go 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: go 3 | 4 | go: 5 | - 1.3 6 | - tip 7 | 8 | services: 9 | - mysql 10 | 11 | before_script: 12 | - mysql -e 'create database LocationTest;' 13 | 14 | script: 15 | - go get 16 | - go get gopkg.in/check.v1 17 | - go test ./... 18 | - go build 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | weather-go 2 | ========== 3 | 4 | [![Build Status](https://travis-ci.org/benschw/weather-go.svg?branch=master)](https://travis-ci.org/benschw/weather-go) 5 | [![GoDoc](http://godoc.org/github.com/benschw/weather-go?status.png)](http://godoc.org/github.com/benschw/weather-go) 6 | 7 | 8 | 9 | Read about this demo app and "Testing Microservices in Go" on [my blog](http://txt.fliglio.com/2014/12/testing-microservices-in-go/) 10 | -------------------------------------------------------------------------------- /config.yaml: -------------------------------------------------------------------------------- 1 | database: "root:@tcp(localhost:3306)/Location?charset=utf8&parseTime=True" 2 | bind: 0.0.0.0:8080 -------------------------------------------------------------------------------- /location/api/location.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | type Location struct { 4 | Id int `json:"id"` 5 | City string `json:"city"` 6 | State string `json:"state"` 7 | Temperature float32 `json:"temperature"` 8 | Weather string `json:"weather"` 9 | } 10 | -------------------------------------------------------------------------------- /location/client/location_client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "fmt" 5 | "github.com/benschw/opin-go/rest" 6 | "github.com/benschw/weather-go/location/api" 7 | "log" 8 | "net/http" 9 | ) 10 | 11 | var _ = log.Print 12 | 13 | type LocationClient struct { 14 | Host string 15 | } 16 | 17 | func (c *LocationClient) AddLocation(city string, state string) (api.Location, error) { 18 | var location api.Location 19 | 20 | newLocation := api.Location{ 21 | City: city, 22 | State: state, 23 | } 24 | 25 | url := fmt.Sprintf("%s/location", c.Host) 26 | r, err := rest.MakeRequest("POST", url, newLocation) 27 | if err != nil { 28 | return location, err 29 | } 30 | err = rest.ProcessResponseEntity(r, &location, http.StatusCreated) 31 | return location, err 32 | } 33 | 34 | func (c *LocationClient) FindAllLocations() ([]api.Location, error) { 35 | var locations []api.Location 36 | 37 | url := fmt.Sprintf("%s/location", c.Host) 38 | r, err := rest.MakeRequest("GET", url, nil) 39 | if err != nil { 40 | return locations, err 41 | } 42 | err = rest.ProcessResponseEntity(r, &locations, http.StatusOK) 43 | return locations, err 44 | } 45 | 46 | func (c *LocationClient) FindLocation(id int) (api.Location, error) { 47 | var location api.Location 48 | 49 | url := fmt.Sprintf("%s/location/%d", c.Host, id) 50 | r, err := rest.MakeRequest("GET", url, nil) 51 | if err != nil { 52 | return location, err 53 | } 54 | err = rest.ProcessResponseEntity(r, &location, http.StatusOK) 55 | return location, err 56 | } 57 | 58 | func (c *LocationClient) SaveLocation(toSave api.Location) (api.Location, error) { 59 | var location api.Location 60 | 61 | url := fmt.Sprintf("%s/location/%d", c.Host, toSave.Id) 62 | r, err := rest.MakeRequest("PUT", url, toSave) 63 | if err != nil { 64 | return location, err 65 | } 66 | err = rest.ProcessResponseEntity(r, &location, http.StatusOK) 67 | return location, err 68 | } 69 | 70 | func (c *LocationClient) DeleteLocation(id int) error { 71 | url := fmt.Sprintf("%s/location/%d", c.Host, id) 72 | r, err := rest.MakeRequest("DELETE", url, nil) 73 | if err != nil { 74 | return err 75 | } 76 | err = rest.ProcessResponseEntity(r, nil, http.StatusNoContent) 77 | return err 78 | } 79 | -------------------------------------------------------------------------------- /location/location_resource.go: -------------------------------------------------------------------------------- 1 | package location 2 | 3 | import ( 4 | "fmt" 5 | "github.com/benschw/opin-go/rest" 6 | "github.com/benschw/weather-go/location/api" 7 | "github.com/jinzhu/gorm" 8 | "log" 9 | "net/http" 10 | ) 11 | 12 | var _ = log.Print 13 | 14 | type LocationResource struct { 15 | Db gorm.DB 16 | WeatherClient WeatherClient 17 | } 18 | 19 | func (r *LocationResource) Add(res http.ResponseWriter, req *http.Request) { 20 | var location api.Location 21 | 22 | if err := rest.Bind(req, &location); err != nil { 23 | rest.SetBadRequestResponse(res) 24 | return 25 | } 26 | if location.City == "" || location.State == "" { 27 | rest.SetBadRequestResponse(res) 28 | return 29 | } 30 | 31 | var found api.Location 32 | if location.Id != 0 && !r.Db.First(&found, location.Id).RecordNotFound() { 33 | rest.SetConflictResponse(res) 34 | return 35 | } 36 | location.Id = 0 37 | 38 | r.Db.Save(&location) 39 | 40 | if err := r.includeConditions(&location); err != nil { 41 | rest.SetInternalServerErrorResponse(res, err) 42 | return 43 | } 44 | if err := rest.SetCreatedResponse(res, location, fmt.Sprintf("location/%d", location.Id)); err != nil { 45 | rest.SetInternalServerErrorResponse(res, err) 46 | return 47 | } 48 | } 49 | 50 | func (r *LocationResource) FindAll(res http.ResponseWriter, req *http.Request) { 51 | var locations []api.Location 52 | 53 | r.Db.Find(&locations) 54 | for i, _ := range locations { 55 | r.includeConditions(&locations[i]) 56 | } 57 | 58 | if err := rest.SetOKResponse(res, locations); err != nil { 59 | rest.SetInternalServerErrorResponse(res, err) 60 | return 61 | } 62 | } 63 | 64 | func (r *LocationResource) Find(res http.ResponseWriter, req *http.Request) { 65 | id, err := rest.PathInt(req, "id") 66 | if err != nil { 67 | rest.SetBadRequestResponse(res) 68 | return 69 | } 70 | var location api.Location 71 | 72 | if r.Db.First(&location, id).RecordNotFound() { 73 | rest.SetNotFoundResponse(res) 74 | return 75 | } 76 | 77 | if err = r.includeConditions(&location); err != nil { 78 | rest.SetInternalServerErrorResponse(res, err) 79 | return 80 | } 81 | if err := rest.SetOKResponse(res, location); err != nil { 82 | rest.SetInternalServerErrorResponse(res, err) 83 | return 84 | } 85 | } 86 | 87 | func (r *LocationResource) Save(res http.ResponseWriter, req *http.Request) { 88 | var location api.Location 89 | 90 | id, err := rest.PathInt(req, "id") 91 | if err != nil { 92 | rest.SetBadRequestResponse(res) 93 | return 94 | } 95 | if err := rest.Bind(req, &location); err != nil { 96 | rest.SetBadRequestResponse(res) 97 | return 98 | } 99 | if location.Id != 0 && location.Id != id { 100 | rest.SetBadRequestResponse(res) 101 | return 102 | } 103 | location.Id = id 104 | if location.City == "" || location.State == "" { 105 | rest.SetBadRequestResponse(res) 106 | return 107 | } 108 | 109 | var found api.Location 110 | if r.Db.First(&found, id).RecordNotFound() { 111 | rest.SetNotFoundResponse(res) 112 | return 113 | } 114 | 115 | r.Db.Save(&location) 116 | 117 | if err = r.includeConditions(&location); err != nil { 118 | rest.SetInternalServerErrorResponse(res, err) 119 | return 120 | } 121 | if err := rest.SetOKResponse(res, location); err != nil { 122 | rest.SetInternalServerErrorResponse(res, err) 123 | return 124 | } 125 | } 126 | 127 | func (r *LocationResource) Delete(res http.ResponseWriter, req *http.Request) { 128 | id, err := rest.PathInt(req, "id") 129 | if err != nil { 130 | rest.SetBadRequestResponse(res) 131 | return 132 | } 133 | var location api.Location 134 | 135 | if r.Db.First(&location, id).RecordNotFound() { 136 | rest.SetNotFoundResponse(res) 137 | return 138 | } 139 | 140 | r.Db.Delete(&location) 141 | 142 | if err = r.includeConditions(&location); err != nil { 143 | rest.SetInternalServerErrorResponse(res, err) 144 | return 145 | } 146 | if err := rest.SetNoContentResponse(res); err != nil { 147 | rest.SetInternalServerErrorResponse(res, err) 148 | return 149 | } 150 | } 151 | 152 | func (r *LocationResource) includeConditions(loc *api.Location) error { 153 | cond, err := r.WeatherClient.FindForLocation(loc.City, loc.State) 154 | if err == nil { 155 | loc.Temperature = cond.Main.Temperature 156 | if len(cond.Weather) > 0 { 157 | loc.Weather = cond.Weather[0].Description 158 | } 159 | } 160 | return err 161 | } 162 | -------------------------------------------------------------------------------- /location/location_service.go: -------------------------------------------------------------------------------- 1 | package location 2 | 3 | import ( 4 | "github.com/benschw/weather-go/location/api" 5 | wclient "github.com/benschw/weather-go/openweather/client" 6 | _ "github.com/go-sql-driver/mysql" 7 | "github.com/gorilla/mux" 8 | "github.com/jinzhu/gorm" 9 | "log" 10 | "net/http" 11 | ) 12 | 13 | var _ = log.Printf 14 | 15 | type LocationService struct { 16 | Bind string 17 | Db gorm.DB 18 | WeatherClient WeatherClient 19 | } 20 | 21 | func NewLocationService(bind string, dbStr string) (*LocationService, error) { 22 | s := &LocationService{} 23 | 24 | db, err := DbOpen(dbStr) 25 | if err != nil { 26 | return s, err 27 | } 28 | 29 | s.Db = db 30 | s.Bind = bind 31 | s.WeatherClient = &wclient.WeatherClient{} 32 | 33 | return s, nil 34 | } 35 | 36 | func (s *LocationService) MigrateDb() error { 37 | 38 | s.Db.AutoMigrate(&api.Location{}) 39 | return nil 40 | } 41 | 42 | func (s *LocationService) Run() error { 43 | 44 | // route handlers 45 | resource := &LocationResource{ 46 | Db: s.Db, 47 | WeatherClient: s.WeatherClient, 48 | } 49 | 50 | // Configure Routes 51 | r := mux.NewRouter() 52 | 53 | r.HandleFunc("/location", resource.Add).Methods("POST") 54 | r.HandleFunc("/location", resource.FindAll).Methods("GET") 55 | r.HandleFunc("/location/{id}", resource.Find).Methods("GET") 56 | r.HandleFunc("/location/{id}", resource.Save).Methods("PUT") 57 | r.HandleFunc("/location/{id}", resource.Delete).Methods("DELETE") 58 | 59 | http.Handle("/", r) 60 | 61 | // Start HTTP Server 62 | return http.ListenAndServe(s.Bind, nil) 63 | } 64 | 65 | func DbOpen(dbStr string) (gorm.DB, error) { 66 | db, err := gorm.Open("mysql", dbStr) 67 | if err != nil { 68 | return db, err 69 | } 70 | db.SingularTable(true) 71 | return db, nil 72 | } 73 | -------------------------------------------------------------------------------- /location/location_service_configuration.go: -------------------------------------------------------------------------------- 1 | package location 2 | 3 | import ( 4 | wapi "github.com/benschw/weather-go/openweather/api" 5 | ) 6 | 7 | type WeatherClient interface { 8 | FindForLocation(city string, state string) (wapi.Conditions, error) 9 | } 10 | -------------------------------------------------------------------------------- /location/location_service_test.go: -------------------------------------------------------------------------------- 1 | package location 2 | 3 | import ( 4 | "fmt" 5 | "github.com/benschw/opin-go/config" 6 | "github.com/benschw/opin-go/rando" 7 | "github.com/benschw/opin-go/rest" 8 | "github.com/benschw/weather-go/location/api" 9 | "github.com/benschw/weather-go/location/client" 10 | . "gopkg.in/check.v1" 11 | "log" 12 | "net/http" 13 | ) 14 | 15 | var _ = fmt.Print 16 | var _ = log.Print 17 | 18 | type TestSuite struct { 19 | s *LocationService 20 | host string 21 | } 22 | 23 | var _ = Suite(&TestSuite{}) 24 | 25 | func (s *TestSuite) SetUpSuite(c *C) { 26 | var cfg struct { 27 | Database string 28 | } 29 | 30 | var _ = config.Bind("../test.yaml", &cfg) 31 | 32 | host := fmt.Sprintf("localhost:%d", rando.Port()) 33 | 34 | db, _ := DbOpen(cfg.Database) 35 | 36 | s.s = &LocationService{ 37 | Db: db, 38 | Bind: host, 39 | WeatherClient: &WeatherClientStub{}, 40 | } 41 | go s.s.Run() 42 | 43 | s.host = "http://" + host 44 | } 45 | 46 | func (s *TestSuite) SetUpTest(c *C) { 47 | s.s.MigrateDb() 48 | } 49 | 50 | func (s *TestSuite) TearDownTest(c *C) { 51 | s.s.Db.DropTable(api.Location{}) 52 | } 53 | 54 | // Location should be added 55 | func (s *TestSuite) TestAdd(c *C) { 56 | // given 57 | locClient := client.LocationClient{Host: s.host} 58 | 59 | // when 60 | created, err := locClient.AddLocation("Austin", "Texas") 61 | 62 | // then 63 | c.Assert(err, Equals, nil) 64 | found, _ := locClient.FindLocation(created.Id) 65 | 66 | c.Assert(created, DeepEquals, found) 67 | } 68 | 69 | // Client should return ErrStatusBadRequest when entity doesn't validate 70 | func (s *TestSuite) TestAddBadRequest(c *C) { 71 | // given 72 | locClient := client.LocationClient{Host: s.host} 73 | 74 | // when 75 | _, err := locClient.AddLocation("", "Texas") 76 | 77 | // then 78 | c.Assert(err, Equals, rest.ErrStatusBadRequest) 79 | } 80 | 81 | // Client should return ErrStatusConflict when id exists 82 | // (not supported by client so pulled impl into test) 83 | func (s *TestSuite) TestAddConflict(c *C) { 84 | // given 85 | locClient := client.LocationClient{Host: s.host} 86 | created, _ := locClient.AddLocation("Austin", "Texas") 87 | 88 | // when 89 | url := fmt.Sprintf("%s/location", s.host) 90 | r, _ := rest.MakeRequest("POST", url, created) 91 | err := rest.ProcessResponseEntity(r, nil, http.StatusCreated) 92 | 93 | // then 94 | c.Assert(err, Equals, rest.ErrStatusConflict) 95 | } 96 | 97 | // Location should be findable 98 | func (s *TestSuite) TestFind(c *C) { 99 | // given 100 | locClient := client.LocationClient{Host: s.host} 101 | created, _ := locClient.AddLocation("Austin", "Texas") 102 | 103 | // when 104 | found, err := locClient.FindLocation(created.Id) 105 | 106 | // then 107 | c.Assert(err, Equals, nil) 108 | 109 | c.Assert(created, DeepEquals, found) 110 | } 111 | 112 | // Client should return ErrStatusNotFound when not found 113 | func (s *TestSuite) TestFindNotFound(c *C) { 114 | // given 115 | locClient := client.LocationClient{Host: s.host} 116 | 117 | // when 118 | _, err := locClient.FindLocation(1) 119 | 120 | // then 121 | c.Assert(err, Equals, rest.ErrStatusNotFound) 122 | } 123 | 124 | // Client should return ErrStatusBadRequest when id doesn't validate 125 | // (not supported by client so pulled impl into test) 126 | func (s *TestSuite) TestFindBadRequest(c *C) { 127 | 128 | // when 129 | url := fmt.Sprintf("%s/location/%s", s.host, "asd") 130 | r, err := rest.MakeRequest("GET", url, nil) 131 | err = rest.ProcessResponseEntity(r, nil, http.StatusOK) 132 | 133 | // then 134 | c.Assert(err, Equals, rest.ErrStatusBadRequest) 135 | } 136 | 137 | // Find all should return all locations 138 | func (s *TestSuite) TestFindAll(c *C) { 139 | // given 140 | locClient := client.LocationClient{Host: s.host} 141 | 142 | loc1, err := locClient.AddLocation("Austin", "Texas") 143 | loc2, err := locClient.AddLocation("Williamsburg", "Virginia") 144 | // when 145 | 146 | foundLocations, err := locClient.FindAllLocations() 147 | 148 | // then 149 | c.Assert(err, Equals, nil) 150 | 151 | c.Assert(foundLocations, DeepEquals, []api.Location{loc1, loc2}) 152 | } 153 | 154 | // Find all should return empty list when no results are found 155 | func (s *TestSuite) TestFindAllEmpty(c *C) { 156 | // given 157 | locClient := client.LocationClient{Host: s.host} 158 | 159 | // when 160 | foundLocations, err := locClient.FindAllLocations() 161 | 162 | // then 163 | c.Assert(err, Equals, nil) 164 | 165 | c.Assert(len(foundLocations), Equals, 0) 166 | } 167 | 168 | // Save should update a location 169 | func (s *TestSuite) TestSave(c *C) { 170 | // given 171 | locClient := client.LocationClient{Host: s.host} 172 | 173 | location, _ := locClient.AddLocation("Austin", "Texas") 174 | 175 | // when 176 | saved, err := locClient.SaveLocation(location) 177 | 178 | // then 179 | c.Assert(err, Equals, nil) 180 | 181 | c.Assert(location.State, DeepEquals, saved.State) 182 | } 183 | 184 | // Client should return ErrStatusNotFound if trying to save to an id that doesn't exist 185 | func (s *TestSuite) TestSaveNotFound(c *C) { 186 | // given 187 | locClient := client.LocationClient{Host: s.host} 188 | 189 | location, _ := locClient.AddLocation("Austin", "Texas") 190 | 191 | // when 192 | location.Id = location.Id + 1 193 | location.State = "foo" 194 | _, err := locClient.SaveLocation(location) 195 | 196 | // then 197 | c.Assert(err, Equals, rest.ErrStatusNotFound) 198 | } 199 | 200 | // Client should return ErrStatusBadRequest if entity doesn't validate 201 | func (s *TestSuite) TestSaveBadRequestFromEntity(c *C) { 202 | // given 203 | locClient := client.LocationClient{Host: s.host} 204 | 205 | location, _ := locClient.AddLocation("Austin", "Texas") 206 | 207 | // when 208 | location.State = "" 209 | _, err := locClient.SaveLocation(location) 210 | 211 | // then 212 | c.Assert(err, Equals, rest.ErrStatusBadRequest) 213 | } 214 | 215 | // Client should return ErrStatusBadRequest if Id doesn't validate 216 | // (not supported by client so pulled impl into test) 217 | func (s *TestSuite) TestSaveBadRequestFromId(c *C) { 218 | // given 219 | locClient := client.LocationClient{Host: s.host} 220 | 221 | location, _ := locClient.AddLocation("Austin", "Texas") 222 | 223 | // when 224 | url := fmt.Sprintf("%s/location/%s", s.host, "asd") 225 | r, err := rest.MakeRequest("GET", url, location) 226 | err = rest.ProcessResponseEntity(r, nil, http.StatusOK) 227 | 228 | // then 229 | c.Assert(err, Equals, rest.ErrStatusBadRequest) 230 | } 231 | 232 | // Delete should Delete a location 233 | func (s *TestSuite) TestDelete(c *C) { 234 | // given 235 | locClient := client.LocationClient{Host: s.host} 236 | 237 | location, _ := locClient.AddLocation("Austin", "Texas") 238 | 239 | // when 240 | err := locClient.DeleteLocation(location.Id) 241 | 242 | // then 243 | c.Assert(err, Equals, nil) 244 | 245 | foundLocations, _ := locClient.FindAllLocations() 246 | 247 | c.Assert(len(foundLocations), Equals, 0) 248 | } 249 | 250 | // Client should return ErrStatusNotFound if trying to delete an Id that doesn't exist 251 | func (s *TestSuite) TestDeleteNotFound(c *C) { 252 | // given 253 | locClient := client.LocationClient{Host: s.host} 254 | 255 | // when 256 | err := locClient.DeleteLocation(1) 257 | 258 | // then 259 | c.Assert(err, Equals, rest.ErrStatusNotFound) 260 | } 261 | 262 | // Client should return ErrStatusBadRequest if Id doesn't validate 263 | // (not supported by client so pulled impl into test) 264 | func (s *TestSuite) TestDeleteBdRequesta(c *C) { 265 | // when 266 | url := fmt.Sprintf("%s/location/%s", s.host, "asd") 267 | r, _ := rest.MakeRequest("DELETE", url, nil) 268 | err := rest.ProcessResponseEntity(r, nil, http.StatusNoContent) 269 | 270 | // then 271 | c.Assert(err, Equals, rest.ErrStatusBadRequest) 272 | } 273 | -------------------------------------------------------------------------------- /location/support_test.go: -------------------------------------------------------------------------------- 1 | package location 2 | 3 | import ( 4 | "fmt" 5 | wapi "github.com/benschw/weather-go/openweather/api" 6 | . "gopkg.in/check.v1" 7 | "log" 8 | "testing" 9 | ) 10 | 11 | var _ = fmt.Print 12 | var _ = log.Print 13 | 14 | func Test(t *testing.T) { TestingT(t) } 15 | 16 | type WeatherClientStub struct { 17 | } 18 | 19 | func (c *WeatherClientStub) FindForLocation(city string, state string) (wapi.Conditions, error) { 20 | if city == "Austin" && state == "Texas" { 21 | return wapi.Conditions{ 22 | Main: wapi.Main{ 23 | Temperature: 75, 24 | }, 25 | Weather: []wapi.Weather{ 26 | wapi.Weather{ 27 | Description: "sunny", 28 | }, 29 | }, 30 | }, nil 31 | } else { 32 | return wapi.Conditions{}, nil 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "github.com/benschw/opin-go/config" 7 | "github.com/benschw/weather-go/location" 8 | "log" 9 | "os" 10 | ) 11 | 12 | func main() { 13 | // Get Arguments 14 | var cfgPath string 15 | 16 | flag.StringVar(&cfgPath, "config", "./config.yaml", "Path to Config File") 17 | 18 | flag.Usage = func() { 19 | fmt.Fprintf(os.Stderr, "Usage: %s [arguments] \n", os.Args[0]) 20 | flag.PrintDefaults() 21 | } 22 | 23 | flag.Parse() 24 | 25 | // Load Config 26 | var cfg struct { 27 | Bind string 28 | Database string 29 | } 30 | if err := config.Bind(cfgPath, &cfg); err != nil { 31 | log.Fatal(err) 32 | } 33 | 34 | // pull desired command/operation from args 35 | if flag.NArg() == 0 { 36 | flag.Usage() 37 | log.Fatal("Command argument required") 38 | } 39 | cmd := flag.Arg(0) 40 | 41 | // Configure Server 42 | s, err := location.NewLocationService(cfg.Bind, cfg.Database) 43 | if err != nil { 44 | log.Fatal(err) 45 | } 46 | // Run Main App 47 | switch cmd { 48 | case "serve": 49 | 50 | // Start Server 51 | if err := s.Run(); err != nil { 52 | log.Fatal(err) 53 | } 54 | case "migrate-db": 55 | 56 | // Start Server 57 | if err := s.MigrateDb(); err != nil { 58 | log.Fatal(err) 59 | } 60 | default: 61 | flag.Usage() 62 | log.Fatalf("Unknown Command: %s", cmd) 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /openweather/api/conditions.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | type Conditions struct { 4 | Main Main `json:"main"` 5 | Weather []Weather `json:"weather"` 6 | } 7 | 8 | type Weather struct { 9 | Description string `json:"description"` 10 | } 11 | 12 | type Main struct { 13 | Temperature float32 `json:"temp"` 14 | } 15 | -------------------------------------------------------------------------------- /openweather/client/weather_client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "fmt" 5 | "github.com/benschw/opin-go/rest" 6 | "github.com/benschw/weather-go/openweather/api" 7 | "log" 8 | "net/http" 9 | ) 10 | 11 | var _ = log.Print 12 | 13 | const UriString string = "http://api.openweathermap.org/data/2.5/weather?units=imperial&q=" //Austin,Texas 14 | 15 | type WeatherClient struct { 16 | } 17 | 18 | func (c *WeatherClient) FindForLocation(city string, state string) (api.Conditions, error) { 19 | var cond api.Conditions 20 | 21 | url := fmt.Sprintf("%s%s,%s", UriString, city, state) 22 | r, err := rest.MakeRequest("GET", url, nil) 23 | if err != nil { 24 | return cond, err 25 | } 26 | err = rest.ProcessResponseEntity(r, &cond, http.StatusOK) 27 | return cond, err 28 | } 29 | -------------------------------------------------------------------------------- /openweather/client/weather_client_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "fmt" 5 | "github.com/benschw/weather-go/openweather/api" 6 | . "gopkg.in/check.v1" 7 | "log" 8 | "testing" 9 | ) 10 | 11 | var _ = fmt.Print 12 | var _ = log.Print 13 | 14 | func Test(t *testing.T) { TestingT(t) } 15 | 16 | type IntTestSuite struct { 17 | } 18 | 19 | var _ = Suite(&IntTestSuite{}) 20 | 21 | // Find should return weather for a city/state 22 | func (s *IntTestSuite) TestFind(c *C) { 23 | // given 24 | client := WeatherClient{} 25 | 26 | // when 27 | cond, err := client.FindForLocation("Austin", "Texas") 28 | 29 | // then 30 | c.Assert(err, Equals, nil) 31 | 32 | c.Assert(cond.Main.Temperature > 0, Equals, true) 33 | c.Assert(cond.Weather[0].Description, Not(Equals), "") 34 | } 35 | 36 | // Client should return empty "Conditions" when a state isn't found 37 | func (s *IntTestSuite) TestFindNotFound(c *C) { 38 | // given 39 | client := WeatherClient{} 40 | 41 | // when 42 | cond, err := client.FindForLocation("Foo", "Bar") 43 | 44 | // then 45 | c.Assert(err, Equals, nil) 46 | 47 | c.Assert(cond, DeepEquals, api.Conditions{}) 48 | } 49 | -------------------------------------------------------------------------------- /openweather/readme.go: -------------------------------------------------------------------------------- 1 | package openweather 2 | 3 | // OpenWeather Package 4 | // 5 | -------------------------------------------------------------------------------- /test.yaml: -------------------------------------------------------------------------------- 1 | database: "root:@tcp(localhost:3306)/LocationTest?charset=utf8&parseTime=True" 2 | --------------------------------------------------------------------------------