Skip to content

Commit f625fd8

Browse files
authored
test: add coverage for defense-in-depth parsing helpers and Inf throttle ratio (#212)
Add 34 new tests covering: - parseFloat64: NaN, Inf, negative, zero, boundary, invalid string - parseFloat64Ratio: NaN, Inf, negative, exceeds ceiling - parseOverheadPercent: NaN, Inf, negative, zero (valid), exceeds max - parseFloat64NonNeg: NaN, Inf, negative, zero, cap-at-one - scaleLimits: normal ratio, zero request/limit, equal ratio - GetThrottleRatio: Inf returns zero (NaN was tested, Inf was not) Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
1 parent 36bc04b commit f625fd8

2 files changed

Lines changed: 188 additions & 0 deletions

File tree

internal/controller/savings_test.go

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,3 +228,174 @@ func TestComputeSavings_Mixed(t *testing.T) {
228228
// Cost increase: 0.2 cores * $0.031/hr * 730 hrs = $4.53
229229
assert.Equal(t, "$4.53", savings.EstimatedMonthlyCostIncrease)
230230
}
231+
232+
// --- Defense-in-depth parsing helpers ---
233+
234+
func TestParseFloat64_ValidValue(t *testing.T) {
235+
assert.Equal(t, 3.5, parseFloat64("3.5", 1.0))
236+
}
237+
238+
func TestParseFloat64_EmptyReturnsFallback(t *testing.T) {
239+
assert.Equal(t, 1.0, parseFloat64("", 1.0))
240+
}
241+
242+
func TestParseFloat64_NaNReturnsFallback(t *testing.T) {
243+
assert.Equal(t, 1.0, parseFloat64("NaN", 1.0))
244+
}
245+
246+
func TestParseFloat64_InfReturnsFallback(t *testing.T) {
247+
assert.Equal(t, 1.0, parseFloat64("Inf", 1.0))
248+
}
249+
250+
func TestParseFloat64_NegativeInfReturnsFallback(t *testing.T) {
251+
assert.Equal(t, 1.0, parseFloat64("-Inf", 1.0))
252+
}
253+
254+
func TestParseFloat64_NegativeReturnsFallback(t *testing.T) {
255+
assert.Equal(t, 1.0, parseFloat64("-0.5", 1.0))
256+
}
257+
258+
func TestParseFloat64_ZeroReturnsFallback(t *testing.T) {
259+
assert.Equal(t, 1.0, parseFloat64("0", 1.0))
260+
}
261+
262+
func TestParseFloat64_ExceedsMaxReturnsFallback(t *testing.T) {
263+
assert.Equal(t, 1.0, parseFloat64("11.0", 1.0))
264+
}
265+
266+
func TestParseFloat64_BoundaryMaxAccepted(t *testing.T) {
267+
assert.Equal(t, 10.0, parseFloat64("10.0", 1.0))
268+
}
269+
270+
func TestParseFloat64_InvalidStringReturnsFallback(t *testing.T) {
271+
assert.Equal(t, 1.0, parseFloat64("abc", 1.0))
272+
}
273+
274+
func TestParseFloat64Ratio_ValidValue(t *testing.T) {
275+
assert.Equal(t, 2.5, parseFloat64Ratio("2.5"))
276+
}
277+
278+
func TestParseFloat64Ratio_EmptyReturnsZero(t *testing.T) {
279+
assert.Equal(t, 0.0, parseFloat64Ratio(""))
280+
}
281+
282+
func TestParseFloat64Ratio_NaNReturnsZero(t *testing.T) {
283+
assert.Equal(t, 0.0, parseFloat64Ratio("NaN"))
284+
}
285+
286+
func TestParseFloat64Ratio_InfReturnsZero(t *testing.T) {
287+
assert.Equal(t, 0.0, parseFloat64Ratio("Inf"))
288+
}
289+
290+
func TestParseFloat64Ratio_NegativeReturnsZero(t *testing.T) {
291+
assert.Equal(t, 0.0, parseFloat64Ratio("-1.0"))
292+
}
293+
294+
func TestParseFloat64Ratio_ExceedsMaxReturnsZero(t *testing.T) {
295+
assert.Equal(t, 0.0, parseFloat64Ratio("1001"))
296+
}
297+
298+
func TestParseFloat64Ratio_BoundaryMaxAccepted(t *testing.T) {
299+
assert.Equal(t, 1000.0, parseFloat64Ratio("1000"))
300+
}
301+
302+
func TestParseOverheadPercent_ValidValue(t *testing.T) {
303+
assert.Equal(t, 20.0, parseOverheadPercent("20", 15.0))
304+
}
305+
306+
func TestParseOverheadPercent_EmptyReturnsFallback(t *testing.T) {
307+
assert.Equal(t, 15.0, parseOverheadPercent("", 15.0))
308+
}
309+
310+
func TestParseOverheadPercent_NaNReturnsFallback(t *testing.T) {
311+
assert.Equal(t, 15.0, parseOverheadPercent("NaN", 15.0))
312+
}
313+
314+
func TestParseOverheadPercent_InfReturnsFallback(t *testing.T) {
315+
assert.Equal(t, 15.0, parseOverheadPercent("Inf", 15.0))
316+
}
317+
318+
func TestParseOverheadPercent_NegativeReturnsFallback(t *testing.T) {
319+
assert.Equal(t, 15.0, parseOverheadPercent("-5", 15.0))
320+
}
321+
322+
func TestParseOverheadPercent_ZeroIsValid(t *testing.T) {
323+
assert.Equal(t, 0.0, parseOverheadPercent("0", 15.0))
324+
}
325+
326+
func TestParseOverheadPercent_ExceedsMaxReturnsFallback(t *testing.T) {
327+
assert.Equal(t, 15.0, parseOverheadPercent("901", 15.0))
328+
}
329+
330+
func TestParseFloat64NonNeg_ValidValue(t *testing.T) {
331+
assert.Equal(t, 0.5, parseFloat64NonNeg("0.5", 0.1))
332+
}
333+
334+
func TestParseFloat64NonNeg_EmptyReturnsFallback(t *testing.T) {
335+
assert.Equal(t, 0.1, parseFloat64NonNeg("", 0.1))
336+
}
337+
338+
func TestParseFloat64NonNeg_NaNReturnsFallback(t *testing.T) {
339+
assert.Equal(t, 0.1, parseFloat64NonNeg("NaN", 0.1))
340+
}
341+
342+
func TestParseFloat64NonNeg_InfReturnsFallback(t *testing.T) {
343+
assert.Equal(t, 0.1, parseFloat64NonNeg("Inf", 0.1))
344+
}
345+
346+
func TestParseFloat64NonNeg_NegativeReturnsFallback(t *testing.T) {
347+
assert.Equal(t, 0.1, parseFloat64NonNeg("-0.5", 0.1))
348+
}
349+
350+
func TestParseFloat64NonNeg_ZeroIsValid(t *testing.T) {
351+
assert.Equal(t, 0.0, parseFloat64NonNeg("0", 0.1))
352+
}
353+
354+
func TestParseFloat64NonNeg_ExceedsOneCapsAtOne(t *testing.T) {
355+
assert.Equal(t, 1.0, parseFloat64NonNeg("5.0", 0.1))
356+
}
357+
358+
func TestParseFloat64NonNeg_ExactlyOneIsValid(t *testing.T) {
359+
assert.Equal(t, 1.0, parseFloat64NonNeg("1.0", 0.1))
360+
}
361+
362+
func TestScaleLimits_NormalCase(t *testing.T) {
363+
// Current limit 1000m with request 500m gives ratio 2.0.
364+
// New request 250m * 2.0 = 500m new limit.
365+
result := scaleLimits(
366+
resource.MustParse("500m"),
367+
resource.MustParse("1000m"),
368+
resource.MustParse("250m"),
369+
)
370+
expected := resource.MustParse("500m")
371+
assert.True(t, result.Cmp(expected) == 0, "expected %s, got %s", expected.String(), result.String())
372+
}
373+
374+
func TestScaleLimits_ZeroRequestReturnsZero(t *testing.T) {
375+
result := scaleLimits(
376+
resource.MustParse("0"),
377+
resource.MustParse("1000m"),
378+
resource.MustParse("250m"),
379+
)
380+
assert.True(t, result.IsZero())
381+
}
382+
383+
func TestScaleLimits_ZeroLimitReturnsZero(t *testing.T) {
384+
result := scaleLimits(
385+
resource.MustParse("500m"),
386+
resource.MustParse("0"),
387+
resource.MustParse("250m"),
388+
)
389+
assert.True(t, result.IsZero())
390+
}
391+
392+
func TestScaleLimits_EqualRequestAndLimit(t *testing.T) {
393+
// Ratio is 1.0 so new limit equals new request.
394+
result := scaleLimits(
395+
resource.MustParse("500m"),
396+
resource.MustParse("500m"),
397+
resource.MustParse("300m"),
398+
)
399+
expected := resource.MustParse("300m")
400+
assert.True(t, result.Cmp(expected) == 0, "expected %s, got %s", expected.String(), result.String())
401+
}

internal/metrics/collector_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,23 @@ func TestGetThrottleRatio_NaNReturnsZero(t *testing.T) {
402402
assert.Zero(t, ratio)
403403
}
404404

405+
func TestGetThrottleRatio_InfReturnsZero(t *testing.T) {
406+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
407+
w.Header().Set("Content-Type", "application/json")
408+
w.WriteHeader(http.StatusOK)
409+
// Prometheus can return +Inf for extreme rate ratios.
410+
_, _ = w.Write([]byte(`{"status":"success","data":{"resultType":"vector","result":[{"metric":{},"value":[1700000000,"+Inf"]}]}}`))
411+
}))
412+
defer server.Close()
413+
414+
collector, err := NewPrometheusCollector(server.URL, logr.Discard(), http.DefaultTransport)
415+
require.NoError(t, err)
416+
417+
ratio, err := collector.GetThrottleRatio(context.Background(), "default", "pod-1", "app", time.Now())
418+
require.NoError(t, err)
419+
assert.Zero(t, ratio)
420+
}
421+
405422
func TestEscapePromQLRegex(t *testing.T) {
406423
tests := []struct {
407424
input string

0 commit comments

Comments
 (0)