mirror of
https://github.com/fnproject/fn.git
synced 2022-10-28 21:29:17 +03:00
Add annotations to routes and apps (#866)
Adds 'annotations' attribute to Routes and Apps
This commit is contained in:
261
api/models/annotations_test.go
Normal file
261
api/models/annotations_test.go
Normal file
@@ -0,0 +1,261 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type testObj struct {
|
||||
Md Annotations `json:"annotations,omitempty"`
|
||||
}
|
||||
|
||||
type myJson struct {
|
||||
Foo string `json:"foo,omitempty"`
|
||||
Bar string `json:"bar,omitempty"`
|
||||
}
|
||||
|
||||
func (m Annotations) withRawKey(key string, val string) Annotations {
|
||||
newMd := make(Annotations)
|
||||
for k, v := range m {
|
||||
newMd[k] = v
|
||||
}
|
||||
|
||||
v := annotationValue([]byte(val))
|
||||
newMd[key] = &v
|
||||
return newMd
|
||||
}
|
||||
|
||||
func mustParseMd(t *testing.T, md string) Annotations {
|
||||
mdObj := make(Annotations)
|
||||
|
||||
err := json.Unmarshal([]byte(md), &mdObj)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse must-parse value %s %v", md, err)
|
||||
}
|
||||
return mdObj
|
||||
}
|
||||
|
||||
func TestAnnotationsEqual(t *testing.T) {
|
||||
annWithVal, _ := EmptyAnnotations().With("foo", "Bar")
|
||||
|
||||
tcs := []struct {
|
||||
a Annotations
|
||||
b Annotations
|
||||
equals bool
|
||||
}{
|
||||
{EmptyAnnotations(), EmptyAnnotations(), true},
|
||||
{annWithVal, EmptyAnnotations(), false},
|
||||
{annWithVal, annWithVal, true},
|
||||
{EmptyAnnotations().withRawKey("v1", `"a"`), EmptyAnnotations().withRawKey("v1", `"b"`), false},
|
||||
{EmptyAnnotations().withRawKey("v1", `"a"`), EmptyAnnotations().withRawKey("v2", `"a"`), false},
|
||||
|
||||
{annWithVal.Without("foo"), EmptyAnnotations(), true},
|
||||
{mustParseMd(t,
|
||||
"{ \r\n\t"+`"md.1":{ `+"\r\n\t"+`
|
||||
|
||||
"subkey1": "value\n with\n newlines",
|
||||
|
||||
"subkey2": true
|
||||
}
|
||||
}`), mustParseMd(t, `{"md.1":{"subkey1":"value\n with\n newlines", "subkey2":true}}`), true},
|
||||
}
|
||||
|
||||
for _, tc := range tcs {
|
||||
if tc.a.Equals(tc.b) != tc.equals {
|
||||
t.Errorf("Annotations equality mismatch - expecting (%v == %v) = %v", tc.b, tc.a, tc.equals)
|
||||
}
|
||||
if tc.b.Equals(tc.a) != tc.equals {
|
||||
t.Errorf("Annotations reflexive equality mismatch - expecting (%v == %v) = %v", tc.b, tc.a, tc.equals)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var annCases = []struct {
|
||||
val *testObj
|
||||
valString string
|
||||
}{
|
||||
{val: &testObj{Md: EmptyAnnotations()}, valString: "{}"},
|
||||
{val: &testObj{Md: EmptyAnnotations().withRawKey("stringval", `"bar"`)}, valString: `{"annotations":{"stringval":"bar"}}`},
|
||||
{val: &testObj{Md: EmptyAnnotations().withRawKey("intval", `1001`)}, valString: `{"annotations":{"intval":1001}}`},
|
||||
{val: &testObj{Md: EmptyAnnotations().withRawKey("floatval", "3.141")}, valString: `{"annotations":{"floatval":3.141}}`},
|
||||
{val: &testObj{Md: EmptyAnnotations().withRawKey("objval", `{"foo":"fooval","bar":"barval"}`)}, valString: `{"annotations":{"objval":{"foo":"fooval","bar":"barval"}}}`},
|
||||
{val: &testObj{Md: EmptyAnnotations().withRawKey("objval", `{"foo":"fooval","bar":{"barbar":"barbarval"}}`)}, valString: `{"annotations":{"objval":{"foo":"fooval","bar":{"barbar":"barbarval"}}}}`},
|
||||
{val: &testObj{Md: EmptyAnnotations().withRawKey("objval", `{"foo":"JSON newline \\n string"}`)}, valString: `{"annotations":{"objval":{"foo":"JSON newline \\n string"}}}`},
|
||||
}
|
||||
|
||||
func TestAnnotationsJSONMarshalling(t *testing.T) {
|
||||
|
||||
for _, tc := range annCases {
|
||||
v, err := json.Marshal(tc.val)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshal json into %s: %v", tc.valString, err)
|
||||
}
|
||||
if string(v) != tc.valString {
|
||||
t.Errorf("Invalid annotations value, expected %s, got %s", tc.valString, string(v))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestAnnotationsJSONUnMarshalling(t *testing.T) {
|
||||
|
||||
for _, tc := range annCases {
|
||||
tv := testObj{}
|
||||
err := json.Unmarshal([]byte(tc.valString), &tv)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to unmarshal json into %s: %v", tc.valString, err)
|
||||
}
|
||||
if !reflect.DeepEqual(&tv, tc.val) {
|
||||
t.Errorf("Invalid annotations value, expected %v, got %v", tc.val, tv)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestAnnotationsWithHonorsKeyLimits(t *testing.T) {
|
||||
var validKeys = []string{
|
||||
"ok",
|
||||
strings.Repeat("a", maxAnnotationKeyBytes),
|
||||
"fnproject/internal/foo",
|
||||
"foo.bar.com.baz",
|
||||
"foo$bar!_+-()[]:@/<>$",
|
||||
}
|
||||
for _, k := range validKeys {
|
||||
m, err := EmptyAnnotations().With(k, "value")
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Should have accepted valid key %s,%v", k, err)
|
||||
}
|
||||
|
||||
err = m.Validate()
|
||||
if err != nil {
|
||||
t.Errorf("Should have validate valid key %s,%v", k, err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var invalidKeys = []struct {
|
||||
key string
|
||||
err APIError
|
||||
}{
|
||||
{"", ErrInvalidAnnotationKey},
|
||||
{" ", ErrInvalidAnnotationKey},
|
||||
{"\u00e9", ErrInvalidAnnotationKey},
|
||||
{"foo bar", ErrInvalidAnnotationKey},
|
||||
{strings.Repeat("a", maxAnnotationKeyBytes+1), ErrInvalidAnnotationKeyLength},
|
||||
}
|
||||
for _, kc := range invalidKeys {
|
||||
_, err := EmptyAnnotations().With(kc.key, "value")
|
||||
if err == nil {
|
||||
t.Errorf("Should have rejected invalid key %s", kc.key)
|
||||
}
|
||||
|
||||
m := EmptyAnnotations().withRawKey(kc.key, "\"data\"")
|
||||
err = m.Validate()
|
||||
if err != kc.err {
|
||||
t.Errorf("Should have returned validation error %v, for key %s got %v", kc.err, kc.key, err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestAnnotationsHonorsValueLimits(t *testing.T) {
|
||||
validValues := []interface{}{
|
||||
"ok",
|
||||
&myJson{Foo: "foo"},
|
||||
strings.Repeat("a", maxAnnotationValueBytes-2),
|
||||
[]string{strings.Repeat("a", maxAnnotationValueBytes-4)},
|
||||
|
||||
1,
|
||||
[]string{"a", "b", "c"},
|
||||
true,
|
||||
}
|
||||
|
||||
for _, v := range validValues {
|
||||
|
||||
_, err := EmptyAnnotations().With("key", v)
|
||||
if err != nil {
|
||||
t.Errorf("Should have accepted valid value %s,%v", v, err)
|
||||
}
|
||||
|
||||
rawJson, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
md := EmptyAnnotations().withRawKey("key", string(rawJson))
|
||||
|
||||
err = md.Validate()
|
||||
if err != nil {
|
||||
t.Errorf("Should have validated valid value successfully %s, got error %v", string(rawJson), err)
|
||||
}
|
||||
}
|
||||
|
||||
invalidValues := []struct {
|
||||
val interface{}
|
||||
err APIError
|
||||
}{
|
||||
{"", ErrInvalidAnnotationValue},
|
||||
{nil, ErrInvalidAnnotationValue},
|
||||
{strings.Repeat("a", maxAnnotationValueBytes-1), ErrInvalidAnnotationValueLength},
|
||||
{[]string{strings.Repeat("a", maxAnnotationValueBytes-3)}, ErrInvalidAnnotationValueLength},
|
||||
}
|
||||
|
||||
for _, v := range invalidValues {
|
||||
_, err := EmptyAnnotations().With("key", v.val)
|
||||
if err == nil {
|
||||
t.Errorf("Should have rejected invalid value \"%v\"", v)
|
||||
}
|
||||
|
||||
rawJson, err := json.Marshal(v.val)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
md := EmptyAnnotations().withRawKey("key", string(rawJson))
|
||||
|
||||
err = md.Validate()
|
||||
if err != v.err {
|
||||
t.Errorf("Expected validation error %v for '%s', got %v", v.err, string(rawJson), err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestMergeAnnotations(t *testing.T) {
|
||||
|
||||
mdWithNKeys := func(n int) Annotations {
|
||||
md := EmptyAnnotations()
|
||||
for i := 0; i < n; i++ {
|
||||
md = md.withRawKey(fmt.Sprintf("key-%d", i), "val")
|
||||
}
|
||||
return md
|
||||
}
|
||||
validCases := []struct {
|
||||
first Annotations
|
||||
second Annotations
|
||||
result Annotations
|
||||
}{
|
||||
{first: EmptyAnnotations(), second: EmptyAnnotations(), result: EmptyAnnotations()},
|
||||
{first: EmptyAnnotations().withRawKey("key1", "\"val\""), second: EmptyAnnotations(), result: EmptyAnnotations().withRawKey("key1", "\"val\"")},
|
||||
{first: EmptyAnnotations(), second: EmptyAnnotations().withRawKey("key1", "\"val\""), result: EmptyAnnotations().withRawKey("key1", "\"val\"")},
|
||||
{first: EmptyAnnotations().withRawKey("key1", "\"val\""), second: EmptyAnnotations().withRawKey("key1", "\"val\""), result: EmptyAnnotations().withRawKey("key1", "\"val\"")},
|
||||
{first: EmptyAnnotations().withRawKey("key1", "\"val1\""), second: EmptyAnnotations().withRawKey("key2", "\"val2\""), result: EmptyAnnotations().withRawKey("key1", "\"val1\"").withRawKey("key2", "\"val2\"")},
|
||||
{first: EmptyAnnotations().withRawKey("key1", "\"val1\""), second: EmptyAnnotations().withRawKey("key1", "\"\""), result: EmptyAnnotations()},
|
||||
{first: EmptyAnnotations().withRawKey("key1", "\"val1\""), second: EmptyAnnotations().withRawKey("key2", "\"\""), result: EmptyAnnotations().withRawKey("key1", "\"val1\"")},
|
||||
{first: mdWithNKeys(maxAnnotationsKeys - 1), second: EmptyAnnotations().withRawKey("newkey", "\"val\""), result: mdWithNKeys(maxAnnotationsKeys-1).withRawKey("newkey", "\"val\"")},
|
||||
}
|
||||
|
||||
for _, v := range validCases {
|
||||
newMd := v.first.MergeChange(v.second)
|
||||
|
||||
if !reflect.DeepEqual(newMd, v.result) {
|
||||
t.Errorf("Change %v + %v : expected %v got %v", v.first, v.second, v.result, newMd)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user