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