github.com/google/osv-scalibr@v0.4.1/enricher/huggingfacemeta/huggingfacemeta_test.go (about) 1 // Copyright 2025 Google LLC 2 // 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 package huggingfacemeta_test 16 17 import ( 18 "encoding/json" 19 "net/http" 20 "net/http/httptest" 21 "testing" 22 "time" 23 24 "github.com/google/go-cmp/cmp" 25 "github.com/google/go-cmp/cmp/cmpopts" 26 "github.com/google/osv-scalibr/enricher/huggingfacemeta" 27 "github.com/google/osv-scalibr/inventory" 28 "github.com/google/osv-scalibr/veles/secrets/huggingfaceapikey" 29 ) 30 31 func TestEnricher(t *testing.T) { 32 type testEnricherSubCase struct { 33 name string 34 respBody any 35 statusCode int 36 input inventory.Inventory 37 want inventory.Inventory 38 wantErr error 39 } 40 validRespBody := func(role string, fineGrainedScope []string) map[string]any { 41 return map[string]any{ 42 "auth": map[string]any{ 43 "accessToken": map[string]any{ 44 "role": role, 45 "fineGrained": map[string]any{ 46 "scoped": []map[string]any{ 47 {"permissions": fineGrainedScope}, 48 }, 49 }, 50 }, 51 }, 52 } 53 } 54 path := "/foo/bar/key.json" 55 cases := []struct { 56 name string 57 subs []testEnricherSubCase 58 }{ 59 { 60 name: "Append_role_and_Fine_Grained_Scopes", 61 subs: []testEnricherSubCase{ 62 { 63 name: "supported", 64 statusCode: http.StatusOK, 65 input: inventory.Inventory{ 66 Secrets: []*inventory.Secret{ 67 { 68 Secret: huggingfaceapikey.HuggingfaceAPIKey{Key: "foo"}, 69 Location: path, 70 }, 71 }, 72 }, 73 respBody: validRespBody("read", 74 []string{"inference.endpoints.infer.write", "repo.content.read"}), 75 want: inventory.Inventory{ 76 Secrets: []*inventory.Secret{ 77 { 78 Secret: huggingfaceapikey.HuggingfaceAPIKey{ 79 Key: "foo", 80 Role: "read", 81 FineGrainedScope: []string{"inference.endpoints.infer.write", "repo.content.read"}, 82 }, 83 Location: path, 84 }, 85 }, 86 }, 87 }, 88 { 89 name: "no json response", 90 statusCode: http.StatusOK, 91 wantErr: cmpopts.AnyError, 92 input: inventory.Inventory{ 93 Secrets: []*inventory.Secret{ 94 { 95 Secret: huggingfaceapikey.HuggingfaceAPIKey{Key: "foo2"}, 96 Location: path, 97 }, 98 }, 99 }, 100 respBody: "response body is not json", 101 want: inventory.Inventory{ 102 Secrets: []*inventory.Secret{ 103 { 104 Secret: huggingfaceapikey.HuggingfaceAPIKey{ 105 Key: "foo2", 106 }, 107 Location: path, 108 }, 109 }, 110 }, 111 }, 112 { 113 name: "non-200 status code", 114 statusCode: http.StatusUnauthorized, // 401 Unauthorized 115 input: inventory.Inventory{ 116 Secrets: []*inventory.Secret{ 117 { 118 Secret: huggingfaceapikey.HuggingfaceAPIKey{Key: "foo3"}, 119 Location: path, 120 }, 121 }, 122 }, 123 want: inventory.Inventory{ 124 Secrets: []*inventory.Secret{ 125 { 126 Secret: huggingfaceapikey.HuggingfaceAPIKey{ 127 Key: "foo3", 128 }, 129 Location: path, 130 }, 131 }, 132 }, 133 }, 134 }, 135 }, 136 } 137 for _, tc := range cases { 138 t.Run(tc.name, func(t *testing.T) { 139 for _, sc := range tc.subs { 140 t.Run(sc.name, func(t *testing.T) { 141 // Mock Hugging Face API server responding with the desired role and fineGrainedScope 142 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 143 if r.URL.Path != "/api/whoami-v2" { 144 http.NotFound(w, r) 145 return 146 } 147 // Return the status code defined in the test case 148 if sc.statusCode != 0 && sc.statusCode != http.StatusOK { 149 w.WriteHeader(sc.statusCode) 150 return 151 } 152 153 w.Header().Set("Content-Type", "application/json") 154 if _, ok := sc.respBody.(string); ok { 155 _, err := w.Write([]byte(sc.respBody.(string))) 156 if err != nil { 157 return 158 } 159 return 160 } 161 _ = json.NewEncoder(w).Encode(sc.respBody) 162 })) 163 defer ts.Close() 164 165 // Use enricher configured against the mock server 166 enricher := huggingfacemeta.NewWithBaseURL(ts.URL) 167 168 err := enricher.Enrich(t.Context(), nil, &sc.input) 169 if !cmp.Equal(err, sc.wantErr, cmpopts.EquateErrors()) { 170 t.Fatalf("Enrich() error: got %v, want %v\n", err, sc.wantErr) 171 } 172 got := &sc.input 173 want := &sc.want 174 // We can rely on the order of Secrets in the inventory here, since the enricher is not supposed to change it. 175 if diff := cmp.Diff(want, got, cmpopts.EquateErrors(), cmpopts.IgnoreTypes(time.Time{})); diff != "" { 176 t.Errorf("Enrich() got diff (-want +got):\n%s", diff) 177 } 178 }) 179 } 180 }) 181 } 182 }