github.com/lusis/distribution@v2.0.1+incompatible/notifications/http_test.go (about) 1 package notifications 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "mime" 7 "net/http" 8 "net/http/httptest" 9 "reflect" 10 "strconv" 11 "testing" 12 13 "github.com/docker/distribution/manifest" 14 ) 15 16 // TestHTTPSink mocks out an http endpoint and notifies it under a couple of 17 // conditions, ensuring correct behavior. 18 func TestHTTPSink(t *testing.T) { 19 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 20 defer r.Body.Close() 21 if r.Method != "POST" { 22 w.WriteHeader(http.StatusMethodNotAllowed) 23 t.Fatalf("unexpected request method: %v", r.Method) 24 return 25 } 26 27 // Extract the content type and make sure it matches 28 contentType := r.Header.Get("Content-Type") 29 mediaType, _, err := mime.ParseMediaType(contentType) 30 if err != nil { 31 w.WriteHeader(http.StatusBadRequest) 32 t.Fatalf("error parsing media type: %v, contenttype=%q", err, contentType) 33 return 34 } 35 36 if mediaType != EventsMediaType { 37 w.WriteHeader(http.StatusUnsupportedMediaType) 38 t.Fatalf("incorrect media type: %q != %q", mediaType, EventsMediaType) 39 return 40 } 41 42 var envelope Envelope 43 dec := json.NewDecoder(r.Body) 44 if err := dec.Decode(&envelope); err != nil { 45 w.WriteHeader(http.StatusBadRequest) 46 t.Fatalf("error decoding request body: %v", err) 47 return 48 } 49 50 // Let caller choose the status 51 status, err := strconv.Atoi(r.FormValue("status")) 52 if err != nil { 53 t.Logf("error parsing status: %v", err) 54 55 // May just be empty, set status to 200 56 status = http.StatusOK 57 } 58 59 w.WriteHeader(status) 60 })) 61 62 metrics := newSafeMetrics() 63 sink := newHTTPSink(server.URL, 0, nil, 64 &endpointMetricsHTTPStatusListener{safeMetrics: metrics}) 65 66 var expectedMetrics EndpointMetrics 67 expectedMetrics.Statuses = make(map[string]int) 68 69 for _, tc := range []struct { 70 events []Event // events to send 71 url string 72 failure bool // true if there should be a failure. 73 statusCode int // if not set, no status code should be incremented. 74 }{ 75 { 76 statusCode: http.StatusOK, 77 events: []Event{ 78 createTestEvent("push", "library/test", manifest.ManifestMediaType)}, 79 }, 80 { 81 statusCode: http.StatusOK, 82 events: []Event{ 83 createTestEvent("push", "library/test", manifest.ManifestMediaType), 84 createTestEvent("push", "library/test", layerMediaType), 85 createTestEvent("push", "library/test", layerMediaType), 86 }, 87 }, 88 { 89 statusCode: http.StatusTemporaryRedirect, 90 }, 91 { 92 statusCode: http.StatusBadRequest, 93 failure: true, 94 }, 95 { 96 // Case where connection never goes through. 97 url: "http://shoudlntresolve/", 98 failure: true, 99 }, 100 } { 101 102 if tc.failure { 103 expectedMetrics.Failures += len(tc.events) 104 } else { 105 expectedMetrics.Successes += len(tc.events) 106 } 107 108 if tc.statusCode > 0 { 109 expectedMetrics.Statuses[fmt.Sprintf("%d %s", tc.statusCode, http.StatusText(tc.statusCode))] += len(tc.events) 110 } 111 112 url := tc.url 113 if url == "" { 114 url = server.URL + "/" 115 } 116 // setup endpoint to respond with expected status code. 117 url += fmt.Sprintf("?status=%v", tc.statusCode) 118 sink.url = url 119 120 t.Logf("testcase: %v, fail=%v", url, tc.failure) 121 // Try a simple event emission. 122 err := sink.Write(tc.events...) 123 124 if !tc.failure { 125 if err != nil { 126 t.Fatalf("unexpected error send event: %v", err) 127 } 128 } else { 129 if err == nil { 130 t.Fatalf("the endpoint should have rejected the request") 131 } 132 } 133 134 if !reflect.DeepEqual(metrics.EndpointMetrics, expectedMetrics) { 135 t.Fatalf("metrics not as expected: %#v != %#v", metrics.EndpointMetrics, expectedMetrics) 136 } 137 } 138 139 if err := sink.Close(); err != nil { 140 t.Fatalf("unexpected error closing http sink: %v", err) 141 } 142 143 // double close returns error 144 if err := sink.Close(); err == nil { 145 t.Fatalf("second close should have returned error: %v", err) 146 } 147 148 } 149 150 func createTestEvent(action, repo, typ string) Event { 151 event := createEvent(action) 152 153 event.Target.MediaType = typ 154 event.Target.Repository = repo 155 156 return *event 157 }