1
0
mirror of https://github.com/redis/go-redis.git synced 2026-10-24 07:59:20 +03:00
Files
go-redis/timeseries_querylabels_unit_test.go
Nedyalko Dyakov ce8d3b053f feat(timeseries): add TS.QUERYLABELS support (#3926)
* feat(timeseries): add TS.QUERYLABELS support (Redis 8.10)

- TSQueryLabels lists label names, TSQueryLabelValues the values of
  one label, across series matching optional TS.QUERYINDEX-style
  filters
- the FILTER token is emitted only when expressions are given (the
  server rejects a bare FILTER); expressions pass through verbatim
  and the VALUES label is matched byte-exactly
- replies are unordered, server-deduplicated string sets under both
  RESP2 and RESP3; empty replies are success, not errors
- wire-format unit tests plus a RESP2/RESP3 integration test that
  gates on the live server supporting the command
- runnable example under example/ts-querylabels

* fix(tests): use skip for tests

* test(timeseries): version-gate TS.QUERYLABELS

Replace the standalone probe test (command probing plus the
REDIS_QUERYLABELS_TEST_ADDR env var) with Ginkgo specs in the main
suite, gated on SkipBeforeRedisVersion("8.10"). The plain go test also
called the Ginkgo-based SkipBeforeRedisVersion helper, which panics
outside the suite when it actually skips; moving the specs into the
suite fixes that. Covers both command forms under RESP2 and RESP3.

* chore(deps): bump grpc to v1.82.1 in otel examples

govulncheck fails every PR on GO-2026-6061 in google.golang.org/grpc
v1.80.0 (indirect via the OTLP exporters); v1.82.1 carries the fix.

---------

Co-authored-by: ofekshenawa <104765379+ofekshenawa@users.noreply.github.com>
2026-07-28 19:40:04 +03:00

76 lines
2.4 KiB
Go

package redis
import (
"context"
"reflect"
"testing"
)
// TestTSQueryLabelsArgs pins the wire format of both TS.QUERYLABELS forms.
// The FILTER token must be emitted only when at least one filter expression
// is given (the server rejects a bare FILTER), filter expressions must be
// passed verbatim and in order, and the VALUES label must not be normalized.
func TestTSQueryLabelsArgs(t *testing.T) {
ctx := context.Background()
tests := []struct {
name string
call func(c cmdable) Cmder
want []interface{}
}{
{
name: "labels_without_filter_omits_filter_token",
call: func(c cmdable) Cmder { return c.TSQueryLabels(ctx, nil) },
want: []interface{}{"TS.QUERYLABELS", "LABELS"},
},
{
name: "labels_with_empty_filter_slice_omits_filter_token",
call: func(c cmdable) Cmder { return c.TSQueryLabels(ctx, []string{}) },
want: []interface{}{"TS.QUERYLABELS", "LABELS"},
},
{
name: "labels_with_one_filter",
call: func(c cmdable) Cmder { return c.TSQueryLabels(ctx, []string{"type=sensor"}) },
want: []interface{}{"TS.QUERYLABELS", "LABELS", "FILTER", "type=sensor"},
},
{
name: "labels_filters_verbatim_and_ordered",
call: func(c cmdable) Cmder {
return c.TSQueryLabels(ctx, []string{"type=sensor", "location!=", "l=(a,b)"})
},
want: []interface{}{"TS.QUERYLABELS", "LABELS", "FILTER", "type=sensor", "location!=", "l=(a,b)"},
},
{
name: "values_without_filter_omits_filter_token",
call: func(c cmdable) Cmder { return c.TSQueryLabelValues(ctx, "location", nil) },
want: []interface{}{"TS.QUERYLABELS", "VALUES", "location"},
},
{
name: "values_with_filters",
call: func(c cmdable) Cmder {
return c.TSQueryLabelValues(ctx, "location", []string{"type=sensor"})
},
want: []interface{}{"TS.QUERYLABELS", "VALUES", "location", "FILTER", "type=sensor"},
},
{
name: "values_label_not_normalized",
call: func(c cmdable) Cmder { return c.TSQueryLabelValues(ctx, " LoCaTiOn ", nil) },
want: []interface{}{"TS.QUERYLABELS", "VALUES", " LoCaTiOn "},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var captured Cmder
c := captureCmdable(&captured)
cmd := tt.call(c)
if cmd == nil {
t.Fatalf("command builder returned nil")
}
if !reflect.DeepEqual(cmd.Args(), tt.want) {
t.Errorf("args mismatch\n got: %#v\nwant: %#v", cmd.Args(), tt.want)
}
})
}
}