knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/test/spoof/response_checks.go (about) 1 /* 2 Copyright 2020 The Knative Authors 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package spoof 18 19 import ( 20 "fmt" 21 "net/http" 22 "strings" 23 "sync" 24 25 "k8s.io/apimachinery/pkg/util/sets" 26 ) 27 28 // MatchesBody checks that the *first* response body matches the "expected" body, otherwise failing. 29 func MatchesBody(expected string) ResponseChecker { 30 return func(resp *Response) (bool, error) { 31 if !strings.Contains(string(resp.Body), expected) { 32 // Returning (true, err) causes SpoofingClient.Poll to fail. 33 return true, fmt.Errorf("body = %s, want: %s", string(resp.Body), expected) 34 } 35 36 return true, nil 37 } 38 } 39 40 // MatchesAllOf combines multiple ResponseCheckers to one ResponseChecker with a logical AND. The 41 // checkers are executed in order. The first function to trigger an error or a retry will short-circuit 42 // the other functions (they will not be executed). 43 // 44 // This is useful for combining a body with a status check like: 45 // MatchesAllOf(IsStatusOK, MatchesBody("test")) 46 // 47 // The MatchesBody check will only be executed after the IsStatusOK has passed. 48 func MatchesAllOf(checkers ...ResponseChecker) ResponseChecker { 49 return func(resp *Response) (bool, error) { 50 for _, checker := range checkers { 51 if done, err := checker(resp); err != nil || !done { 52 return done, err 53 } 54 } 55 return true, nil 56 } 57 } 58 59 // MatchesAllBodies checks that the *first* response body matches the "expected" body, otherwise failing. 60 func MatchesAllBodies(all ...string) ResponseChecker { 61 var m sync.Mutex 62 // This helps with two things: 63 // 1. we can use Equal on sets 64 // 2. it will collapse the duplicates 65 want := sets.New[string](all...) 66 seen := make(sets.Set[string], len(all)) 67 68 return func(resp *Response) (bool, error) { 69 bs := string(resp.Body) 70 for expected := range want { 71 if !strings.Contains(bs, expected) { 72 // See if the next one matches. 73 continue 74 } 75 76 m.Lock() 77 defer m.Unlock() 78 seen.Insert(expected) 79 80 // Stop once we've seen them all. 81 return want.Equal(seen), nil 82 } 83 84 // Returning (true, err) causes SpoofingClient.Poll to fail. 85 return true, fmt.Errorf("body = %s, want one of: %s", bs, all) 86 } 87 } 88 89 // IsStatusOK checks that the response code is a 200. 90 func IsStatusOK(resp *Response) (bool, error) { 91 return IsOneOfStatusCodes(http.StatusOK)(resp) 92 } 93 94 // IsOneOfStatusCodes checks that the response code is equal to the given one. 95 func IsOneOfStatusCodes(codes ...int) ResponseChecker { 96 return func(resp *Response) (bool, error) { 97 for _, code := range codes { 98 if resp.StatusCode == code { 99 return true, nil 100 } 101 } 102 103 return true, fmt.Errorf("status = %d %s, want one of: %v", resp.StatusCode, resp.Status, codes) 104 } 105 }