github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/charm/testing/mockstore.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package testing 5 6 import ( 7 "bytes" 8 "encoding/json" 9 "fmt" 10 "io" 11 "net" 12 "net/http" 13 "os" 14 "strconv" 15 "strings" 16 17 "github.com/juju/loggo" 18 "github.com/juju/utils" 19 gc "launchpad.net/gocheck" 20 21 "github.com/juju/juju/charm" 22 ) 23 24 var logger = loggo.GetLogger("juju.charm.testing.mockstore") 25 26 // MockStore provides a mock charm store implementation useful when testing. 27 type MockStore struct { 28 mux *http.ServeMux 29 listener net.Listener 30 bundleBytes []byte 31 bundleSha256 string 32 Downloads []*charm.URL 33 DownloadsNoStats []*charm.URL 34 Authorizations []string 35 Metadata []string 36 InfoRequestCount int 37 InfoRequestCountNoStats int 38 DefaultSeries string 39 40 charms map[string]int 41 } 42 43 // NewMockStore creates a mock charm store containing the specified charms. 44 func NewMockStore(c *gc.C, charms map[string]int) *MockStore { 45 s := &MockStore{charms: charms, DefaultSeries: "precise"} 46 f, err := os.Open(Charms.BundlePath(c.MkDir(), "dummy")) 47 c.Assert(err, gc.IsNil) 48 defer f.Close() 49 buf := &bytes.Buffer{} 50 s.bundleSha256, _, err = utils.ReadSHA256(io.TeeReader(f, buf)) 51 c.Assert(err, gc.IsNil) 52 s.bundleBytes = buf.Bytes() 53 c.Assert(err, gc.IsNil) 54 s.mux = http.NewServeMux() 55 s.mux.HandleFunc("/charm-info", s.serveInfo) 56 s.mux.HandleFunc("/charm-event", s.serveEvent) 57 s.mux.HandleFunc("/charm/", s.serveCharm) 58 lis, err := net.Listen("tcp", "127.0.0.1:0") 59 c.Assert(err, gc.IsNil) 60 s.listener = lis 61 go http.Serve(s.listener, s) 62 return s 63 } 64 65 // Close closes the mock store's socket. 66 func (s *MockStore) Close() { 67 s.listener.Close() 68 } 69 70 // Address returns the URL used to make requests to the mock store. 71 func (s *MockStore) Address() string { 72 return "http://" + s.listener.Addr().String() 73 } 74 75 // UpdateStoreRevision sets the revision of the specified charm to rev. 76 func (s *MockStore) UpdateStoreRevision(ch string, rev int) { 77 s.charms[ch] = rev 78 } 79 80 // ServeHTTP implements http.ServeHTTP 81 func (s *MockStore) ServeHTTP(w http.ResponseWriter, r *http.Request) { 82 s.mux.ServeHTTP(w, r) 83 } 84 85 func (s *MockStore) serveInfo(w http.ResponseWriter, r *http.Request) { 86 if metadata := r.Header.Get("Juju-Metadata"); metadata != "" { 87 s.Metadata = append(s.Metadata, metadata) 88 logger.Infof("Juju metadata: " + metadata) 89 } 90 91 r.ParseForm() 92 if r.Form.Get("stats") == "0" { 93 s.InfoRequestCountNoStats += 1 94 } else { 95 s.InfoRequestCount += 1 96 } 97 98 response := map[string]*charm.InfoResponse{} 99 for _, url := range r.Form["charms"] { 100 cr := &charm.InfoResponse{} 101 response[url] = cr 102 charmURL, err := charm.ParseURL(url) 103 if err == charm.ErrUnresolvedUrl { 104 ref, _, err := charm.ParseReference(url) 105 if err != nil { 106 panic(err) 107 } 108 if s.DefaultSeries == "" { 109 panic(fmt.Errorf("mock store lacks a default series cannot resolve charm URL: %q", url)) 110 } 111 charmURL = &charm.URL{Reference: ref, Series: s.DefaultSeries} 112 } 113 switch charmURL.Name { 114 case "borken": 115 cr.Errors = append(cr.Errors, "badness") 116 case "terracotta": 117 cr.Errors = append(cr.Errors, "cannot get revision") 118 case "unwise": 119 cr.Warnings = append(cr.Warnings, "foolishness") 120 fallthrough 121 default: 122 if rev, ok := s.charms[charmURL.WithRevision(-1).String()]; ok { 123 if charmURL.Revision == -1 { 124 cr.Revision = rev 125 } else { 126 cr.Revision = charmURL.Revision 127 } 128 cr.Sha256 = s.bundleSha256 129 cr.CanonicalURL = charmURL.String() 130 } else { 131 cr.Errors = append(cr.Errors, "entry not found") 132 } 133 } 134 } 135 data, err := json.Marshal(response) 136 if err != nil { 137 panic(err) 138 } 139 w.Header().Set("Content-Type", "application/json") 140 _, err = w.Write(data) 141 if err != nil { 142 panic(err) 143 } 144 } 145 146 func (s *MockStore) serveEvent(w http.ResponseWriter, r *http.Request) { 147 r.ParseForm() 148 response := map[string]*charm.EventResponse{} 149 for _, url := range r.Form["charms"] { 150 digest := "" 151 if i := strings.Index(url, "@"); i >= 0 { 152 digest = url[i+1:] 153 url = url[:i] 154 } 155 er := &charm.EventResponse{} 156 response[url] = er 157 if digest != "" && digest != "the-digest" { 158 er.Kind = "not-found" 159 er.Errors = []string{"entry not found"} 160 continue 161 } 162 charmURL := charm.MustParseURL(url) 163 switch charmURL.Name { 164 case "borken": 165 er.Kind = "publish-error" 166 er.Errors = append(er.Errors, "badness") 167 case "unwise": 168 er.Warnings = append(er.Warnings, "foolishness") 169 fallthrough 170 default: 171 if rev, ok := s.charms[charmURL.WithRevision(-1).String()]; ok { 172 er.Kind = "published" 173 er.Revision = rev 174 er.Digest = "the-digest" 175 } else { 176 er.Kind = "not-found" 177 er.Errors = []string{"entry not found"} 178 } 179 } 180 } 181 data, err := json.Marshal(response) 182 if err != nil { 183 panic(err) 184 } 185 w.Header().Set("Content-Type", "application/json") 186 _, err = w.Write(data) 187 if err != nil { 188 panic(err) 189 } 190 } 191 192 func (s *MockStore) serveCharm(w http.ResponseWriter, r *http.Request) { 193 charmURL := charm.MustParseURL("cs:" + r.URL.Path[len("/charm/"):]) 194 195 r.ParseForm() 196 if r.Form.Get("stats") == "0" { 197 s.DownloadsNoStats = append(s.DownloadsNoStats, charmURL) 198 } else { 199 s.Downloads = append(s.Downloads, charmURL) 200 } 201 202 if auth := r.Header.Get("Authorization"); auth != "" { 203 s.Authorizations = append(s.Authorizations, auth) 204 } 205 206 w.Header().Set("Connection", "close") 207 w.Header().Set("Content-Type", "application/octet-stream") 208 w.Header().Set("Content-Length", strconv.Itoa(len(s.bundleBytes))) 209 _, err := w.Write(s.bundleBytes) 210 if err != nil { 211 panic(err) 212 } 213 }