/ common / fingerprints / preload / version_detection_test.go
version_detection_test.go
  1  // Copyright (c) 2024-2026 Tencent Zhuque Lab. All rights reserved.
  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  // Requirement: Any integration or derivative work must explicitly attribute
 16  // Tencent Zhuque Lab (https://github.com/Tencent/AI-Infra-Guard) in its
 17  // documentation or user interface, as detailed in the NOTICE file.
 18  
 19  package preload
 20  
 21  import (
 22  	"net/http"
 23  	"net/http/httptest"
 24  	"testing"
 25  	"time"
 26  
 27  	"github.com/Tencent/AI-Infra-Guard/common/fingerprints/parser"
 28  	"github.com/Tencent/AI-Infra-Guard/pkg/httpx"
 29  	"github.com/stretchr/testify/assert"
 30  )
 31  
 32  func newHTTPXForTest(t *testing.T) *httpx.HTTPX {
 33  	t.Helper()
 34  	httpOptions := &httpx.HTTPOptions{
 35  		Timeout:          5 * time.Second,
 36  		RetryMax:         1,
 37  		FollowRedirects:  false,
 38  		HTTPProxy:        "",
 39  		Unsafe:           false,
 40  		DefaultUserAgent: httpx.GetRandomUserAgent(),
 41  		Dialer:           nil,
 42  	}
 43  	hp, err := httpx.NewHttpx(httpOptions)
 44  	assert.NoError(t, err)
 45  	return hp
 46  }
 47  
 48  func TestEvalFpVersionExact(t *testing.T) {
 49  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 50  		if r.URL.Path == "/exact" {
 51  			w.Header().Set("X-Version", "1.2.3")
 52  			_, _ = w.Write([]byte("ok"))
 53  			return
 54  		}
 55  		http.NotFound(w, r)
 56  	}))
 57  	defer server.Close()
 58  
 59  	data := `info:
 60    name: test
 61    author: test
 62    severity: info
 63    metadata:
 64      product: test
 65      vendor: test
 66  version:
 67    - method: GET
 68      path: '/exact'
 69      extractor:
 70        part: header
 71        group: '1'
 72        regex: 'X-Version:\s*([0-9.]+)'
 73  `
 74  	fp, err := parser.InitFingerPrintFromData([]byte(data))
 75  	assert.NoError(t, err)
 76  
 77  	hp := newHTTPXForTest(t)
 78  	version, err := EvalFpVersion(server.URL, hp, *fp)
 79  	assert.NoError(t, err)
 80  	assert.Equal(t, "1.2.3", version)
 81  }
 82  
 83  func TestEvalFpVersionFuzzyIntersection(t *testing.T) {
 84  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 85  		switch r.URL.Path {
 86  		case "/fuzzy1":
 87  			_, _ = w.Write([]byte("range-one"))
 88  		case "/fuzzy2":
 89  			_, _ = w.Write([]byte("range-two"))
 90  		default:
 91  			http.NotFound(w, r)
 92  		}
 93  	}))
 94  	defer server.Close()
 95  
 96  	data := `info:
 97    name: test
 98    author: test
 99    severity: info
100    metadata:
101      product: test
102      vendor: test
103  version:
104    - method: GET
105      path: '/fuzzy1'
106      matchers:
107        - body="range-one"
108      versionrange: '>=1.0.0,<2.0.0'
109    - method: GET
110      path: '/fuzzy2'
111      matchers:
112        - body="range-two"
113      versionrange: '>=1.5.0,<3.0.0'
114  `
115  	fp, err := parser.InitFingerPrintFromData([]byte(data))
116  	assert.NoError(t, err)
117  
118  	hp := newHTTPXForTest(t)
119  	version, err := EvalFpVersion(server.URL, hp, *fp)
120  	assert.NoError(t, err)
121  	assert.Equal(t, ">=1.5.0,<2.0.0", version)
122  }
123  
124  func TestEvalFpVersionFuzzyHashIntersection(t *testing.T) {
125  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
126  		switch r.URL.Path {
127  		case "/fuzzy1":
128  			_, _ = w.Write([]byte("range-one"))
129  		case "/fuzzy2":
130  			_, _ = w.Write([]byte("range-two"))
131  		default:
132  			http.NotFound(w, r)
133  		}
134  	}))
135  	defer server.Close()
136  
137  	data := `info:
138    name: test
139    author: test
140    severity: info
141    metadata:
142      product: test
143      vendor: test
144  version:
145    - method: GET
146      path: '/fuzzy1'
147      matchers:
148        - hash=="012c14d354f49d6e682efaa1e8d3f1433ff7da7093b2b5964aac1302303f52b4"
149      versionrange: '>=1.0.0,<2.0.0'
150    - method: GET
151      path: '/fuzzy2'
152      matchers:
153        - hash=="1773f6ec9285e9d638a94a19375353fa9c8c891c732d80a996724ed8017fe196"
154      versionrange: '>=1.5.0,<3.0.0'
155  `
156  	fp, err := parser.InitFingerPrintFromData([]byte(data))
157  	assert.NoError(t, err)
158  
159  	hp := newHTTPXForTest(t)
160  	version, err := EvalFpVersion(server.URL, hp, *fp)
161  	assert.NoError(t, err)
162  	assert.Equal(t, ">=1.5.0,<2.0.0", version)
163  }
164  
165  func TestEvalFpVersionFuzzyNoMatch(t *testing.T) {
166  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
167  		_, _ = w.Write([]byte("no-match"))
168  	}))
169  	defer server.Close()
170  
171  	data := `info:
172    name: test
173    author: test
174    severity: info
175    metadata:
176      product: test
177      vendor: test
178  version:
179    - method: GET
180      path: '/fuzzy'
181      matchers:
182        - body="another"
183      versionrange: '>=1.0.0,<2.0.0'
184  `
185  	fp, err := parser.InitFingerPrintFromData([]byte(data))
186  	assert.NoError(t, err)
187  
188  	hp := newHTTPXForTest(t)
189  	version, err := EvalFpVersion(server.URL, hp, *fp)
190  	assert.NoError(t, err)
191  	assert.Equal(t, "", version)
192  }