mirror of
https://github.com/projectcapsule/capsule.git
synced 2026-08-18 20:17:04 +00:00
868 lines
19 KiB
Go
868 lines
19 KiB
Go
// Copyright 2020-2026 Project Capsule Authors
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package jsonpath
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
)
|
|
|
|
func TestEvaluateTruthyFromCompiled(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
path string
|
|
object unstructured.Unstructured
|
|
want bool
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "empty missing path is false",
|
|
path: ".missing",
|
|
object: newUnstructured(map[string]any{
|
|
"spec": map[string]any{
|
|
"type": "ClusterIP",
|
|
},
|
|
}),
|
|
want: false,
|
|
},
|
|
{
|
|
name: "empty string is false",
|
|
path: ".spec.value",
|
|
object: newUnstructured(map[string]any{
|
|
"spec": map[string]any{
|
|
"value": "",
|
|
},
|
|
}),
|
|
want: false,
|
|
},
|
|
{
|
|
name: "whitespace string is false",
|
|
path: ".spec.value",
|
|
object: newUnstructured(map[string]any{
|
|
"spec": map[string]any{
|
|
"value": " ",
|
|
},
|
|
}),
|
|
want: false,
|
|
},
|
|
{
|
|
name: "false string is false",
|
|
path: ".spec.value",
|
|
object: newUnstructured(map[string]any{
|
|
"spec": map[string]any{
|
|
"value": "false",
|
|
},
|
|
}),
|
|
want: false,
|
|
},
|
|
{
|
|
name: "false string case insensitive is false",
|
|
path: ".spec.value",
|
|
object: newUnstructured(map[string]any{
|
|
"spec": map[string]any{
|
|
"value": "FALSE",
|
|
},
|
|
}),
|
|
want: false,
|
|
},
|
|
{
|
|
name: "false string with whitespace is false",
|
|
path: ".spec.value",
|
|
object: newUnstructured(map[string]any{
|
|
"spec": map[string]any{
|
|
"value": " false ",
|
|
},
|
|
}),
|
|
want: false,
|
|
},
|
|
{
|
|
name: "zero string is false",
|
|
path: ".spec.value",
|
|
object: newUnstructured(map[string]any{
|
|
"spec": map[string]any{
|
|
"value": "0",
|
|
},
|
|
}),
|
|
want: false,
|
|
},
|
|
{
|
|
name: "true string is true",
|
|
path: ".spec.value",
|
|
object: newUnstructured(map[string]any{
|
|
"spec": map[string]any{
|
|
"value": "true",
|
|
},
|
|
}),
|
|
want: true,
|
|
},
|
|
{
|
|
name: "non-empty scalar string is true",
|
|
path: ".spec.type",
|
|
object: newUnstructured(map[string]any{
|
|
"spec": map[string]any{
|
|
"type": "ClusterIP",
|
|
},
|
|
}),
|
|
want: true,
|
|
},
|
|
{
|
|
name: "one string is true",
|
|
path: ".spec.value",
|
|
object: newUnstructured(map[string]any{
|
|
"spec": map[string]any{
|
|
"value": "1",
|
|
},
|
|
}),
|
|
want: true,
|
|
},
|
|
{
|
|
name: "jsonpath filter match is true",
|
|
path: ".spec.accessModes[?(@==\"ReadWriteOnce\")]",
|
|
object: newUnstructured(map[string]any{
|
|
"spec": map[string]any{
|
|
"accessModes": []any{
|
|
"ReadWriteOnce",
|
|
"ReadOnlyMany",
|
|
},
|
|
},
|
|
}),
|
|
want: true,
|
|
},
|
|
{
|
|
name: "jsonpath filter no match is false",
|
|
path: ".spec.accessModes[?(@==\"ReadWriteMany\")]",
|
|
object: newUnstructured(map[string]any{
|
|
"spec": map[string]any{
|
|
"accessModes": []any{
|
|
"ReadWriteOnce",
|
|
"ReadOnlyMany",
|
|
},
|
|
},
|
|
}),
|
|
want: false,
|
|
},
|
|
{
|
|
name: "invalid jsonpath execution returns error",
|
|
path: ".spec.accessModes[999]",
|
|
object: newUnstructured(map[string]any{
|
|
"spec": map[string]any{
|
|
"accessModes": []any{
|
|
"ReadWriteOnce",
|
|
},
|
|
},
|
|
}),
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
compiled, err := CompileJSONPath(tt.path)
|
|
if err != nil {
|
|
t.Fatalf("expected jsonpath %q to compile, got %v", tt.path, err)
|
|
}
|
|
|
|
got, err := EvaluateTruthyFromCompiled(tt.object, compiled)
|
|
if tt.wantErr {
|
|
if err == nil {
|
|
t.Fatal("expected error, got nil")
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
if err != nil {
|
|
t.Fatalf("expected no error, got %v", err)
|
|
}
|
|
|
|
if got != tt.want {
|
|
t.Fatalf("expected %t, got %t", tt.want, got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestEvaluateTruthyFromCompiledRejectsNilCompiledJSONPath(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
got, err := EvaluateTruthyFromCompiled(newUnstructured(nil), nil)
|
|
if err == nil {
|
|
t.Fatal("expected error, got nil")
|
|
}
|
|
|
|
if got {
|
|
t.Fatal("expected false result for nil compiled jsonpath")
|
|
}
|
|
|
|
if !strings.Contains(err.Error(), "compiled jsonpath is nil") {
|
|
t.Fatalf("expected nil compiled jsonpath error, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestSplitFieldSelectorEquals(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
raw string
|
|
wantPath string
|
|
wantValue string
|
|
wantOK bool
|
|
}{
|
|
{
|
|
name: "single equals",
|
|
raw: `.spec.type=ClusterIP`,
|
|
wantPath: `.spec.type`,
|
|
wantValue: `ClusterIP`,
|
|
wantOK: true,
|
|
},
|
|
{
|
|
name: "double equals",
|
|
raw: `.spec.type==ClusterIP`,
|
|
wantPath: `.spec.type`,
|
|
wantValue: `ClusterIP`,
|
|
wantOK: true,
|
|
},
|
|
{
|
|
name: "double equals with quoted value",
|
|
raw: `.spec.type=="ClusterIP"`,
|
|
wantPath: `.spec.type`,
|
|
wantValue: `ClusterIP`,
|
|
wantOK: true,
|
|
},
|
|
{
|
|
name: "double equals with single quoted value",
|
|
raw: `.spec.type=='ClusterIP'`,
|
|
wantPath: `.spec.type`,
|
|
wantValue: `ClusterIP`,
|
|
wantOK: true,
|
|
},
|
|
{
|
|
name: "trims whitespace around expression",
|
|
raw: ` .spec.type == "ClusterIP" `,
|
|
wantPath: `.spec.type`,
|
|
wantValue: `ClusterIP`,
|
|
wantOK: true,
|
|
},
|
|
{
|
|
name: "keeps spaces inside quoted value",
|
|
raw: `.metadata.annotations["example.com/value"]=="hello world"`,
|
|
wantPath: `.metadata.annotations["example.com/value"]`,
|
|
wantValue: `hello world`,
|
|
wantOK: true,
|
|
},
|
|
{
|
|
name: "quoted value containing equals",
|
|
raw: `.metadata.annotations["example.com/check"]=="a=b"`,
|
|
wantPath: `.metadata.annotations["example.com/check"]`,
|
|
wantValue: `a=b`,
|
|
wantOK: true,
|
|
},
|
|
{
|
|
name: "single quoted value containing equals",
|
|
raw: `.metadata.annotations["example.com/check"]=='a=b'`,
|
|
wantPath: `.metadata.annotations["example.com/check"]`,
|
|
wantValue: `a=b`,
|
|
wantOK: true,
|
|
},
|
|
{
|
|
name: "top level equality after bracket expression",
|
|
raw: `.metadata.labels["app.kubernetes.io/name"]=="nginx"`,
|
|
wantPath: `.metadata.labels["app.kubernetes.io/name"]`,
|
|
wantValue: `nginx`,
|
|
wantOK: true,
|
|
},
|
|
{
|
|
name: "jsonpath filter equality is not top level equality",
|
|
raw: `.spec.accessModes[?(@=="ReadWriteOnce")]`,
|
|
wantOK: false,
|
|
},
|
|
{
|
|
name: "jsonpath filter single equals is not top level equality",
|
|
raw: `.spec.accessModes[?(@="ReadWriteOnce")]`,
|
|
wantOK: false,
|
|
},
|
|
{
|
|
name: "truthy path without equals",
|
|
raw: `.spec.storageClassName`,
|
|
wantOK: false,
|
|
},
|
|
{
|
|
name: "empty string",
|
|
raw: ``,
|
|
wantOK: false,
|
|
},
|
|
{
|
|
name: "whitespace string",
|
|
raw: ` `,
|
|
wantOK: false,
|
|
},
|
|
{
|
|
name: "missing path",
|
|
raw: `=ClusterIP`,
|
|
wantOK: false,
|
|
},
|
|
{
|
|
name: "missing value",
|
|
raw: `.spec.type=`,
|
|
wantOK: false,
|
|
},
|
|
{
|
|
name: "missing value with double equals",
|
|
raw: `.spec.type==`,
|
|
wantOK: false,
|
|
},
|
|
{
|
|
name: "does not split equals inside double quoted path segment",
|
|
raw: `.metadata.annotations["example.com/a=b"]=="value"`,
|
|
wantPath: `.metadata.annotations["example.com/a=b"]`,
|
|
wantValue: `value`,
|
|
wantOK: true,
|
|
},
|
|
{
|
|
name: "does not split equals inside single quoted path segment",
|
|
raw: `.metadata.annotations['example.com/a=b']=="value"`,
|
|
wantPath: `.metadata.annotations['example.com/a=b']`,
|
|
wantValue: `value`,
|
|
wantOK: true,
|
|
},
|
|
{
|
|
name: "does not split equals inside parentheses",
|
|
raw: `.spec.values(@=="ignored")=="value"`,
|
|
wantPath: `.spec.values(@=="ignored")`,
|
|
wantValue: `value`,
|
|
wantOK: true,
|
|
},
|
|
{
|
|
name: "does not split equals inside braces",
|
|
raw: `.spec.values{"a==b"}=="value"`,
|
|
wantPath: `.spec.values{"a==b"}`,
|
|
wantValue: `value`,
|
|
wantOK: true,
|
|
},
|
|
{
|
|
name: "escaped quote inside quoted value",
|
|
raw: `.metadata.annotations["example.com/check"]=="a\"=b"`,
|
|
wantPath: `.metadata.annotations["example.com/check"]`,
|
|
wantValue: `a\"=b`,
|
|
wantOK: true,
|
|
},
|
|
{
|
|
name: "unmatched quote after equality remains part of value",
|
|
raw: `.spec.type=="ClusterIP`,
|
|
wantPath: `.spec.type`,
|
|
wantValue: `"ClusterIP`,
|
|
wantOK: true,
|
|
},
|
|
{
|
|
name: "unmatched quote before equality suppresses split",
|
|
raw: `.metadata.annotations["broken]==value`,
|
|
wantOK: false,
|
|
},
|
|
{
|
|
name: "unmatched bracket suppresses split",
|
|
raw: `.metadata.annotations["key"=="value"`,
|
|
wantOK: false,
|
|
},
|
|
{
|
|
name: "top level equality before later malformed content still splits",
|
|
raw: `.spec.type=ClusterIP[?(@=="ignored")]`,
|
|
wantPath: `.spec.type`,
|
|
wantValue: `ClusterIP[?(@=="ignored")]`,
|
|
wantOK: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
gotPath, gotValue, gotOK := SplitFieldSelectorEquals(tt.raw)
|
|
if gotOK != tt.wantOK {
|
|
t.Fatalf("expected ok=%t, got %t", tt.wantOK, gotOK)
|
|
}
|
|
|
|
if gotPath != tt.wantPath {
|
|
t.Fatalf("expected path %q, got %q", tt.wantPath, gotPath)
|
|
}
|
|
|
|
if gotValue != tt.wantValue {
|
|
t.Fatalf("expected value %q, got %q", tt.wantValue, gotValue)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFindTopLevelEquals(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
raw string
|
|
wantIdx int
|
|
wantWidth int
|
|
}{
|
|
{
|
|
name: "single equals",
|
|
raw: `.spec.type=ClusterIP`,
|
|
wantIdx: len(`.spec.type`),
|
|
wantWidth: 1,
|
|
},
|
|
{
|
|
name: "double equals",
|
|
raw: `.spec.type==ClusterIP`,
|
|
wantIdx: len(`.spec.type`),
|
|
wantWidth: 2,
|
|
},
|
|
{
|
|
name: "ignores equals inside brackets",
|
|
raw: `.spec.accessModes[?(@=="ReadWriteOnce")]`,
|
|
wantIdx: -1,
|
|
wantWidth: 0,
|
|
},
|
|
{
|
|
name: "finds top level equals after brackets",
|
|
raw: `.metadata.labels["app.kubernetes.io/name"]=="nginx"`,
|
|
wantIdx: len(`.metadata.labels["app.kubernetes.io/name"]`),
|
|
wantWidth: 2,
|
|
},
|
|
{
|
|
name: "ignores equals inside double quotes",
|
|
raw: `.metadata.annotations["a=b"]`,
|
|
wantIdx: -1,
|
|
wantWidth: 0,
|
|
},
|
|
{
|
|
name: "ignores equals inside single quotes",
|
|
raw: `.metadata.annotations['a=b']`,
|
|
wantIdx: -1,
|
|
wantWidth: 0,
|
|
},
|
|
{
|
|
name: "ignores escaped quote before equals inside quoted string",
|
|
raw: `.metadata.annotations["a\"=b"]`,
|
|
wantIdx: -1,
|
|
wantWidth: 0,
|
|
},
|
|
{
|
|
name: "ignores equals inside parentheses",
|
|
raw: `.spec.values(@=="ignored")`,
|
|
wantIdx: -1,
|
|
wantWidth: 0,
|
|
},
|
|
{
|
|
name: "ignores equals inside braces",
|
|
raw: `.spec.values{"a==b"}`,
|
|
wantIdx: -1,
|
|
wantWidth: 0,
|
|
},
|
|
{
|
|
name: "finds first top level equals",
|
|
raw: `.spec.type=ClusterIP=ignored`,
|
|
wantIdx: len(`.spec.type`),
|
|
wantWidth: 1,
|
|
},
|
|
{
|
|
name: "empty string",
|
|
raw: ``,
|
|
wantIdx: -1,
|
|
wantWidth: 0,
|
|
},
|
|
{
|
|
name: "no equals",
|
|
raw: `.spec.type`,
|
|
wantIdx: -1,
|
|
wantWidth: 0,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
gotIdx, gotWidth := findTopLevelEquals(tt.raw)
|
|
if gotIdx != tt.wantIdx {
|
|
t.Fatalf("expected idx %d, got %d", tt.wantIdx, gotIdx)
|
|
}
|
|
|
|
if gotWidth != tt.wantWidth {
|
|
t.Fatalf("expected width %d, got %d", tt.wantWidth, gotWidth)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTrimMatchingQuotes(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
value string
|
|
want string
|
|
}{
|
|
{
|
|
name: "empty",
|
|
value: "",
|
|
want: "",
|
|
},
|
|
{
|
|
name: "single character",
|
|
value: `"`,
|
|
want: `"`,
|
|
},
|
|
{
|
|
name: "double quoted",
|
|
value: `"ClusterIP"`,
|
|
want: "ClusterIP",
|
|
},
|
|
{
|
|
name: "single quoted",
|
|
value: `'ClusterIP'`,
|
|
want: "ClusterIP",
|
|
},
|
|
{
|
|
name: "trims inner whitespace",
|
|
value: `" ClusterIP "`,
|
|
want: "ClusterIP",
|
|
},
|
|
{
|
|
name: "unmatched starting quote",
|
|
value: `"ClusterIP`,
|
|
want: `"ClusterIP`,
|
|
},
|
|
{
|
|
name: "unmatched ending quote",
|
|
value: `ClusterIP"`,
|
|
want: `ClusterIP"`,
|
|
},
|
|
{
|
|
name: "different quote types",
|
|
value: `"ClusterIP'`,
|
|
want: `"ClusterIP'`,
|
|
},
|
|
{
|
|
name: "unquoted",
|
|
value: `ClusterIP`,
|
|
want: `ClusterIP`,
|
|
},
|
|
{
|
|
name: "quoted value containing equals",
|
|
value: `"a=b"`,
|
|
want: `a=b`,
|
|
},
|
|
{
|
|
name: "quoted value containing escaped quote",
|
|
value: `"a\"=b"`,
|
|
want: `a\"=b`,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
if got := trimMatchingQuotes(tt.value); got != tt.want {
|
|
t.Fatalf("expected %q, got %q", tt.want, got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSplitFieldSelectorNotEquals(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
raw string
|
|
wantPath string
|
|
wantValue string
|
|
wantOK bool
|
|
}{
|
|
{
|
|
name: "simple not-equals",
|
|
raw: `.status.phase!=Succeeded`,
|
|
wantPath: `.status.phase`,
|
|
wantValue: `Succeeded`,
|
|
wantOK: true,
|
|
},
|
|
{
|
|
name: "not-equals with double-quoted value",
|
|
raw: `.status.phase!="Succeeded"`,
|
|
wantPath: `.status.phase`,
|
|
wantValue: `Succeeded`,
|
|
wantOK: true,
|
|
},
|
|
{
|
|
name: "not-equals with single-quoted value",
|
|
raw: `.status.phase!='Succeeded'`,
|
|
wantPath: `.status.phase`,
|
|
wantValue: `Succeeded`,
|
|
wantOK: true,
|
|
},
|
|
{
|
|
name: "trims whitespace around expression",
|
|
raw: ` .status.phase != "Succeeded" `,
|
|
wantPath: `.status.phase`,
|
|
wantValue: `Succeeded`,
|
|
wantOK: true,
|
|
},
|
|
{
|
|
name: "keeps spaces inside quoted value",
|
|
raw: `.metadata.annotations["example.com/value"]!="hello world"`,
|
|
wantPath: `.metadata.annotations["example.com/value"]`,
|
|
wantValue: `hello world`,
|
|
wantOK: true,
|
|
},
|
|
{
|
|
name: "value containing not-equals in quotes",
|
|
raw: `.metadata.annotations["example.com/check"]!="a!=b"`,
|
|
wantPath: `.metadata.annotations["example.com/check"]`,
|
|
wantValue: `a!=b`,
|
|
wantOK: true,
|
|
},
|
|
{
|
|
name: "top level not-equals after bracket expression",
|
|
raw: `.metadata.labels["app.kubernetes.io/name"]!="nginx"`,
|
|
wantPath: `.metadata.labels["app.kubernetes.io/name"]`,
|
|
wantValue: `nginx`,
|
|
wantOK: true,
|
|
},
|
|
{
|
|
name: "bang without equals is not not-equals",
|
|
raw: `.spec.type!ClusterIP`,
|
|
wantOK: false,
|
|
},
|
|
{
|
|
name: "truthy path without not-equals",
|
|
raw: `.spec.storageClassName`,
|
|
wantOK: false,
|
|
},
|
|
{
|
|
name: "plain equals is not not-equals",
|
|
raw: `.spec.type=ClusterIP`,
|
|
wantOK: false,
|
|
},
|
|
{
|
|
name: "double equals is not not-equals",
|
|
raw: `.spec.type==ClusterIP`,
|
|
wantOK: false,
|
|
},
|
|
{
|
|
name: "empty string",
|
|
raw: ``,
|
|
wantOK: false,
|
|
},
|
|
{
|
|
name: "whitespace string",
|
|
raw: ` `,
|
|
wantOK: false,
|
|
},
|
|
{
|
|
name: "missing path",
|
|
raw: `!=Succeeded`,
|
|
wantOK: false,
|
|
},
|
|
{
|
|
name: "missing value",
|
|
raw: `.status.phase!=`,
|
|
wantOK: false,
|
|
},
|
|
{
|
|
name: "does not split not-equals inside double-quoted path segment",
|
|
raw: `.metadata.annotations["example.com/a!=b"]!="value"`,
|
|
wantPath: `.metadata.annotations["example.com/a!=b"]`,
|
|
wantValue: `value`,
|
|
wantOK: true,
|
|
},
|
|
{
|
|
name: "does not split not-equals inside single-quoted path segment",
|
|
raw: `.metadata.annotations['example.com/a!=b']!="value"`,
|
|
wantPath: `.metadata.annotations['example.com/a!=b']`,
|
|
wantValue: `value`,
|
|
wantOK: true,
|
|
},
|
|
{
|
|
name: "does not split not-equals inside brackets",
|
|
raw: `.spec.accessModes[?(@!="ReadWriteOnce")]!="value"`,
|
|
wantPath: `.spec.accessModes[?(@!="ReadWriteOnce")]`,
|
|
wantValue: `value`,
|
|
wantOK: true,
|
|
},
|
|
{
|
|
name: "does not split not-equals inside parentheses",
|
|
raw: `.spec.values(@!="ignored")!="value"`,
|
|
wantPath: `.spec.values(@!="ignored")`,
|
|
wantValue: `value`,
|
|
wantOK: true,
|
|
},
|
|
{
|
|
name: "does not split not-equals inside braces",
|
|
raw: `.spec.values{"a!=b"}!="value"`,
|
|
wantPath: `.spec.values{"a!=b"}`,
|
|
wantValue: `value`,
|
|
wantOK: true,
|
|
},
|
|
{
|
|
name: "unmatched bracket suppresses split",
|
|
raw: `.metadata.annotations["key"!="value"`,
|
|
wantOK: false,
|
|
},
|
|
{
|
|
name: "unmatched quote after not-equals remains part of value",
|
|
raw: `.status.phase!="Succeeded`,
|
|
wantPath: `.status.phase`,
|
|
wantValue: `"Succeeded`,
|
|
wantOK: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
gotPath, gotValue, gotOK := SplitFieldSelectorNotEquals(tt.raw)
|
|
if gotOK != tt.wantOK {
|
|
t.Fatalf("expected ok=%t, got %t", tt.wantOK, gotOK)
|
|
}
|
|
|
|
if gotPath != tt.wantPath {
|
|
t.Fatalf("expected path %q, got %q", tt.wantPath, gotPath)
|
|
}
|
|
|
|
if gotValue != tt.wantValue {
|
|
t.Fatalf("expected value %q, got %q", tt.wantValue, gotValue)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFindTopLevelNotEquals(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
raw string
|
|
wantIdx int
|
|
wantWidth int
|
|
}{
|
|
{
|
|
name: "simple not-equals",
|
|
raw: `.status.phase!=Succeeded`,
|
|
wantIdx: len(`.status.phase`),
|
|
wantWidth: 2,
|
|
},
|
|
{
|
|
name: "ignores not-equals inside brackets",
|
|
raw: `.spec.accessModes[?(@!="ReadWriteOnce")]`,
|
|
wantIdx: -1,
|
|
wantWidth: 0,
|
|
},
|
|
{
|
|
name: "finds top level not-equals after brackets",
|
|
raw: `.metadata.labels["app.kubernetes.io/name"]!="nginx"`,
|
|
wantIdx: len(`.metadata.labels["app.kubernetes.io/name"]`),
|
|
wantWidth: 2,
|
|
},
|
|
{
|
|
name: "ignores not-equals inside double quotes",
|
|
raw: `.metadata.annotations["a!=b"]`,
|
|
wantIdx: -1,
|
|
wantWidth: 0,
|
|
},
|
|
{
|
|
name: "ignores not-equals inside single quotes",
|
|
raw: `.metadata.annotations['a!=b']`,
|
|
wantIdx: -1,
|
|
wantWidth: 0,
|
|
},
|
|
{
|
|
name: "ignores escaped quote before not-equals inside quoted string",
|
|
raw: `.metadata.annotations["a\"!=b"]`,
|
|
wantIdx: -1,
|
|
wantWidth: 0,
|
|
},
|
|
{
|
|
name: "ignores not-equals inside parentheses",
|
|
raw: `.spec.values(@!="ignored")`,
|
|
wantIdx: -1,
|
|
wantWidth: 0,
|
|
},
|
|
{
|
|
name: "ignores not-equals inside braces",
|
|
raw: `.spec.values{"a!=b"}`,
|
|
wantIdx: -1,
|
|
wantWidth: 0,
|
|
},
|
|
{
|
|
name: "finds first top level not-equals",
|
|
raw: `.status.phase!=Succeeded!=ignored`,
|
|
wantIdx: len(`.status.phase`),
|
|
wantWidth: 2,
|
|
},
|
|
{
|
|
name: "bang without equals is not not-equals",
|
|
raw: `.spec.type!ClusterIP`,
|
|
wantIdx: -1,
|
|
wantWidth: 0,
|
|
},
|
|
{
|
|
name: "plain equals is not found",
|
|
raw: `.spec.type=ClusterIP`,
|
|
wantIdx: -1,
|
|
wantWidth: 0,
|
|
},
|
|
{
|
|
name: "empty string",
|
|
raw: ``,
|
|
wantIdx: -1,
|
|
wantWidth: 0,
|
|
},
|
|
{
|
|
name: "no operator",
|
|
raw: `.spec.type`,
|
|
wantIdx: -1,
|
|
wantWidth: 0,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
gotIdx, gotWidth := findTopLevelNotEquals(tt.raw)
|
|
if gotIdx != tt.wantIdx {
|
|
t.Fatalf("expected idx %d, got %d", tt.wantIdx, gotIdx)
|
|
}
|
|
|
|
if gotWidth != tt.wantWidth {
|
|
t.Fatalf("expected width %d, got %d", tt.wantWidth, gotWidth)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func newUnstructured(object map[string]any) unstructured.Unstructured {
|
|
return unstructured.Unstructured{
|
|
Object: object,
|
|
}
|
|
}
|