Files
nats-server/server/subject_transform_test.go
Jean-Noël Moyne 449b27535e [ADDED] Support for multi-filter in stream sources (#4276)
- [X] Tests added
- [X] Branch rebased on top of current main (`git pull --rebase origin
main`)
- [X] Changes squashed to a single commit (described
[here](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html))
 - [X] Build is green in Travis CI
- [X] You have certified that the contribution is your original work and
that you license the work to the project under the [Apache 2
license](https://github.com/nats-io/nats-server/blob/main/LICENSE)

### Changes proposed in this pull request:

Adds support for multi-filter (and associated transform destinations) to
stream sources

---------

Signed-off-by: Jean-Noël Moyne <jnmoyne@gmail.com>
2023-08-01 10:50:11 -07:00

204 lines
8.1 KiB
Go

// Copyright 2023 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"errors"
"reflect"
"testing"
)
func TestPlaceHolderIndex(t *testing.T) {
testString := "$1"
transformType, indexes, nbPartitions, _, err := indexPlaceHolders(testString)
var position int32
if err != nil || transformType != Wildcard || len(indexes) != 1 || indexes[0] != 1 || nbPartitions != -1 {
t.Fatalf("Error parsing %s", testString)
}
testString = "{{partition(10,1,2,3)}}"
transformType, indexes, nbPartitions, _, err = indexPlaceHolders(testString)
if err != nil || transformType != Partition || !reflect.DeepEqual(indexes, []int{1, 2, 3}) || nbPartitions != 10 {
t.Fatalf("Error parsing %s", testString)
}
testString = "{{ Partition (10,1,2,3) }}"
transformType, indexes, nbPartitions, _, err = indexPlaceHolders(testString)
if err != nil || transformType != Partition || !reflect.DeepEqual(indexes, []int{1, 2, 3}) || nbPartitions != 10 {
t.Fatalf("Error parsing %s", testString)
}
testString = "{{wildcard(2)}}"
transformType, indexes, nbPartitions, _, err = indexPlaceHolders(testString)
if err != nil || transformType != Wildcard || len(indexes) != 1 || indexes[0] != 2 || nbPartitions != -1 {
t.Fatalf("Error parsing %s", testString)
}
testString = "{{SplitFromLeft(2,1)}}"
transformType, indexes, position, _, err = indexPlaceHolders(testString)
if err != nil || transformType != SplitFromLeft || len(indexes) != 1 || indexes[0] != 2 || position != 1 {
t.Fatalf("Error parsing %s", testString)
}
testString = "{{SplitFromRight(3,2)}}"
transformType, indexes, position, _, err = indexPlaceHolders(testString)
if err != nil || transformType != SplitFromRight || len(indexes) != 1 || indexes[0] != 3 || position != 2 {
t.Fatalf("Error parsing %s", testString)
}
testString = "{{SliceFromLeft(2,2)}}"
transformType, indexes, sliceSize, _, err := indexPlaceHolders(testString)
if err != nil || transformType != SliceFromLeft || len(indexes) != 1 || indexes[0] != 2 || sliceSize != 2 {
t.Fatalf("Error parsing %s", testString)
}
}
func TestSubjectTransformHelpers(t *testing.T) {
equals := func(a, b []string) bool {
if len(a) != len(b) {
return false
}
for i, v := range a {
if v != b[i] {
return false
}
}
return true
}
filter, placeHolders := transformUntokenize("bar")
if filter != "bar" || len(placeHolders) != 0 {
t.Fatalf("transformUntokenize for not returning expected result")
}
filter, placeHolders = transformUntokenize("foo.$2.$1")
if filter != "foo.*.*" || !equals(placeHolders, []string{"$2", "$1"}) {
t.Fatalf("transformUntokenize for not returning expected result")
}
filter, placeHolders = transformUntokenize("foo.{{wildcard(2)}}.{{wildcard(1)}}")
if filter != "foo.*.*" || !equals(placeHolders, []string{"{{wildcard(2)}}", "{{wildcard(1)}}"}) {
t.Fatalf("transformUntokenize for not returning expected result")
}
newReversibleTransform := func(src, dest string) *subjectTransform {
tr, err := NewSubjectTransformStrict(src, dest)
if err != nil {
t.Fatalf("Error getting reversible transform: %s to %s", src, dest)
}
return tr
}
tr := newReversibleTransform("foo.*.*", "bar.$2.{{Wildcard(1)}}")
subject := "foo.b.a"
transformed := tr.TransformSubject(subject)
reverse := tr.reverse()
if reverse.TransformSubject(transformed) != subject {
t.Fatal("Reversed transform subject not matching")
}
}
func TestSubjectTransforms(t *testing.T) {
shouldErr := func(src, dest string, strict bool) {
t.Helper()
if _, err := NewSubjectTransformWithStrict(src, dest, strict); err != ErrBadSubject && !errors.Is(err, ErrInvalidMappingDestination) {
t.Fatalf("Did not get an error for src=%q and dest=%q", src, dest)
}
}
// Must be valid subjects.
shouldErr("foo..", "bar", false)
// Wildcards are allowed in src, but must be matched by token placements on the other side.
// e.g. foo.* -> bar.$1.
// Need to have as many pwcs as placements on other side
shouldErr("foo.*", "bar.*", false)
shouldErr("foo.*", "bar.$2", false) // Bad pwc token identifier
shouldErr("foo.*", "bar.$1.>", false) // fwcs have to match.
shouldErr("foo.>", "bar.baz", false) // fwcs have to match.
shouldErr("foo.*.*", "bar.$2", true) // Must place all pwcs.
shouldErr("foo.*", "foo.$foo", true) // invalid $ value
shouldErr("foo.*", "bar.{{Partition(2,1)}}", true) // can only use Wildcard function (and old-style $x) in import transform
shouldErr("foo.*", "foo.{{wildcard(2)}}", false) // Mapping function being passed an out of range wildcard index
shouldErr("foo.*", "foo.{{unimplemented(1)}}", false) // Mapping trying to use an unknown mapping function
shouldErr("foo.*", "foo.{{partition(10)}}", false) // Not enough arguments passed to the mapping function
shouldErr("foo.*", "foo.{{wildcard(foo)}}", false) // Invalid argument passed to the mapping function
shouldErr("foo.*", "foo.{{wildcard()}}", false) // Not enough arguments passed to the mapping function
shouldErr("foo.*", "foo.{{wildcard(1,2)}}", false) // Too many arguments passed to the mapping function
shouldErr("foo.*", "foo.{{ wildcard5) }}", false) // Bad mapping function
shouldErr("foo.*", "foo.{{splitLeft(2,2}}", false) // arg out of range
shouldErr("foo", "bla.{{wildcard(1)}}", false) // arg out of range with no wildcard in the source
shouldBeOK := func(src, dest string, strict bool) *subjectTransform {
t.Helper()
tr, err := NewSubjectTransformWithStrict(src, dest, strict)
if err != nil {
t.Fatalf("Got an error %v for src=%q and dest=%q", err, src, dest)
}
return tr
}
shouldBeOK("foo.*", "bar.{{Wildcard(1)}}", true)
shouldBeOK("foo.*.*", "bar.$2", false) // don't have to use all pwcs.
shouldBeOK("foo.*.*", "bar.{{wildcard(1)}}", false) // don't have to use all pwcs.
shouldBeOK("foo", "bar", false)
shouldBeOK("foo.*.bar.*.baz", "req.$2.$1", false)
shouldBeOK("baz.>", "mybaz.>", false)
shouldBeOK("*", "{{splitfromleft(1,1)}}", false)
shouldBeOK("", "prefix.>", false)
shouldBeOK("*.*", "{{partition(10,1,2)}}", false)
shouldBeOK("foo.*.*", "foo.{{wildcard(1)}}.{{wildcard(2)}}.{{partition(5,1,2)}}", false)
shouldMatch := func(src, dest, sample, expected string) {
t.Helper()
tr := shouldBeOK(src, dest, false)
if tr != nil {
s, err := tr.Match(sample)
if err != nil {
t.Fatalf("Got an error %v when expecting a match for %q to %q", err, sample, expected)
}
if s != expected {
t.Fatalf("Dest does not match what was expected. Got %q, expected %q", s, expected)
}
}
}
shouldMatch("", "prefix.>", "foo", "prefix.foo")
shouldMatch("foo", "", "foo", "foo")
shouldMatch("foo", "bar", "foo", "bar")
shouldMatch("foo.*.bar.*.baz", "req.$2.$1", "foo.A.bar.B.baz", "req.B.A")
shouldMatch("foo.*.bar.*.baz", "req.{{wildcard(2)}}.{{wildcard(1)}}", "foo.A.bar.B.baz", "req.B.A")
shouldMatch("baz.>", "my.pre.>", "baz.1.2.3", "my.pre.1.2.3")
shouldMatch("baz.>", "foo.bar.>", "baz.1.2.3", "foo.bar.1.2.3")
shouldMatch("*", "foo.bar.$1", "foo", "foo.bar.foo")
shouldMatch("*", "{{splitfromleft(1,3)}}", "12345", "123.45")
shouldMatch("*", "{{SplitFromRight(1,3)}}", "12345", "12.345")
shouldMatch("*", "{{SliceFromLeft(1,3)}}", "1234567890", "123.456.789.0")
shouldMatch("*", "{{SliceFromRight(1,3)}}", "1234567890", "1.234.567.890")
shouldMatch("*", "{{split(1,-)}}", "-abc-def--ghi-", "abc.def.ghi")
shouldMatch("*", "{{split(1,-)}}", "abc-def--ghi-", "abc.def.ghi")
shouldMatch("*.*", "{{split(2,-)}}.{{splitfromleft(1,2)}}", "foo.-abc-def--ghij-", "abc.def.ghij.fo.o") // combo + checks split for multiple instance of deliminator and deliminator being at the start or end
}