oras.land/oras-go/v2@v2.5.1-0.20240520045656-aef90e4d04c4/internal/registryutil/proxy_test.go (about) 1 /* 2 Copyright The ORAS Authors. 3 Licensed under the Apache License, Version 2.0 (the "License"); 4 you may not use this file except in compliance with the License. 5 You may obtain a copy of the License at 6 7 http://www.apache.org/licenses/LICENSE-2.0 8 9 Unless required by applicable law or agreed to in writing, software 10 distributed under the License is distributed on an "AS IS" BASIS, 11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 See the License for the specific language governing permissions and 13 limitations under the License. 14 */ 15 16 package registryutil_test 17 18 import ( 19 "bytes" 20 "context" 21 "io" 22 "net/http" 23 "net/http/httptest" 24 "net/url" 25 "reflect" 26 "strconv" 27 "strings" 28 "testing" 29 30 "github.com/opencontainers/go-digest" 31 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 32 "oras.land/oras-go/v2/internal/cas" 33 "oras.land/oras-go/v2/internal/registryutil" 34 "oras.land/oras-go/v2/registry/remote" 35 ) 36 37 func TestProxy_FetchReference(t *testing.T) { 38 content := []byte(`{"manifests":[]}`) 39 desc := ocispec.Descriptor{ 40 MediaType: ocispec.MediaTypeImageIndex, 41 Digest: digest.FromBytes(content), 42 Size: int64(len(content)), 43 } 44 ref := "foobar" 45 // prepare repository server 46 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 47 if r.Method != http.MethodHead && r.Method != http.MethodGet { 48 t.Errorf("unexpected access: %s %s", r.Method, r.URL) 49 w.WriteHeader(http.StatusMethodNotAllowed) 50 return 51 } 52 switch r.URL.Path { 53 case "/v2/test/manifests/" + desc.Digest.String(), 54 "/v2/test/manifests/" + ref: 55 if accept := r.Header.Get("Accept"); !strings.Contains(accept, desc.MediaType) { 56 t.Errorf("manifest not convertable: %s", accept) 57 w.WriteHeader(http.StatusBadRequest) 58 return 59 } 60 w.Header().Set("Content-Type", desc.MediaType) 61 w.Header().Set("Docker-Content-Digest", desc.Digest.String()) 62 w.Header().Set("Content-Length", strconv.Itoa(int(desc.Size))) 63 if r.Method == http.MethodGet { 64 if _, err := w.Write(content); err != nil { 65 t.Errorf("failed to write %q: %v", r.URL, err) 66 } 67 } 68 69 default: 70 t.Errorf("unexpected access: %s %s", r.Method, r.URL) 71 w.WriteHeader(http.StatusNotFound) 72 } 73 })) 74 defer ts.Close() 75 uri, err := url.Parse(ts.URL) 76 if err != nil { 77 t.Fatalf("invalid test http server: %v", err) 78 } 79 80 repoName := uri.Host + "/test" 81 repo, err := remote.NewRepository(repoName) 82 if err != nil { 83 t.Fatalf("NewRepository() error = %v", err) 84 } 85 repo.PlainHTTP = true 86 87 s := registryutil.NewProxy(repo, cas.NewMemory()) 88 ctx := context.Background() 89 90 // first FetchReference 91 exists, err := s.Exists(ctx, desc) 92 if err != nil { 93 t.Fatal("Proxy.Exists() error =", err) 94 } 95 if !exists { 96 t.Errorf("Proxy.Exists() = %v, want %v", exists, true) 97 } 98 gotDesc, rc, err := s.FetchReference(ctx, ref) 99 if err != nil { 100 t.Fatal("Proxy.FetchReference() error =", err) 101 } 102 if !reflect.DeepEqual(gotDesc, desc) { 103 t.Errorf("Proxy.FetchReference() = %v, want %v", gotDesc, desc) 104 } 105 got, err := io.ReadAll(rc) 106 if err != nil { 107 t.Fatal("Proxy.FetchReference().Read() error =", err) 108 } 109 err = rc.Close() 110 if err != nil { 111 t.Error("Proxy.FetchReference().Close() error =", err) 112 } 113 if !bytes.Equal(got, content) { 114 t.Errorf("Proxy.FetchReference() = %v, want %v", got, content) 115 } 116 117 // the subsequent fetch should not touch base CAS 118 // nil base will generate panic if the base CAS is touched 119 s.ReadOnlyStorage = nil 120 121 exists, err = s.Exists(ctx, desc) 122 if err != nil { 123 t.Fatal("Proxy.Exists() error =", err) 124 } 125 if !exists { 126 t.Errorf("Proxy.Exists() = %v, want %v", exists, true) 127 } 128 rc, err = s.Fetch(ctx, desc) 129 if err != nil { 130 t.Fatal("Proxy.Fetch() error =", err) 131 } 132 got, err = io.ReadAll(rc) 133 if err != nil { 134 t.Fatal("Proxy.Fetch().Read() error =", err) 135 } 136 err = rc.Close() 137 if err != nil { 138 t.Error("Proxy.Fetch().Close() error =", err) 139 } 140 if !bytes.Equal(got, content) { 141 t.Errorf("Proxy.Fetch() = %v, want %v", got, content) 142 } 143 144 // repeated FetchReference should succeed 145 exists, err = s.Exists(ctx, desc) 146 if err != nil { 147 t.Fatal("Proxy.Exists() error =", err) 148 } 149 if !exists { 150 t.Errorf("Proxy.Exists() = %v, want %v", exists, true) 151 } 152 gotDesc, rc, err = s.FetchReference(ctx, ref) 153 if err != nil { 154 t.Fatal("Proxy.FetchReference() error =", err) 155 } 156 if !reflect.DeepEqual(gotDesc, desc) { 157 t.Errorf("Proxy.FetchReference() = %v, want %v", gotDesc, desc) 158 } 159 got, err = io.ReadAll(rc) 160 if err != nil { 161 t.Fatal("Proxy.Fetch().Read() error =", err) 162 } 163 err = rc.Close() 164 if err != nil { 165 t.Error("Proxy.Fetch().Close() error =", err) 166 } 167 if !bytes.Equal(got, content) { 168 t.Errorf("Proxy.Fetch() = %v, want %v", got, content) 169 } 170 }