blob: fc7b43c27c22df7ceb4a643f99ef1f47baf2d8e7 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 store
import (
"encoding/json"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/apisix/manager-api/internal/core/entity"
)
type TestObj struct {
Name string `json:"name"`
Email string `json:"email"`
Age int `json:"age"`
}
func TestJsonSchemaValidator_Validate(t *testing.T) {
tests := []struct {
givePath string
giveObj interface{}
wantNewErr error
wantValidateErr []error
}{
{
givePath: "./test_case.json",
giveObj: TestObj{
Name: "lessName",
Email: "too long name greater than 10",
Age: 12,
},
wantValidateErr: []error{
fmt.Errorf("name: String length must be greater than or equal to 10\nemail: String length must be less than or equal to 10"),
fmt.Errorf("email: String length must be less than or equal to 10\nname: String length must be greater than or equal to 10"),
},
},
}
for _, tc := range tests {
v, err := NewJsonSchemaValidator(tc.givePath)
if err != nil {
assert.Equal(t, tc.wantNewErr, err)
continue
}
err = v.Validate(tc.giveObj)
ret := false
for _, wantErr := range tc.wantValidateErr {
if wantErr.Error() == err.Error() {
ret = true
}
}
assert.True(t, ret)
}
}
func TestAPISIXJsonSchemaValidator_Validate(t *testing.T) {
validator, err := NewAPISIXJsonSchemaValidator("main.consumer")
assert.Nil(t, err)
consumer := &entity.Consumer{}
reqBody := `{
"username": "jack",
"plugins": {
"limit-count": {
"count": 2,
"time_window": 60,
"rejected_code": 503,
"key": "remote_addr",
"policy": "local"
}
},
"desc": "test description"
}`
err = json.Unmarshal([]byte(reqBody), consumer)
assert.Nil(t, err)
err = validator.Validate(consumer)
assert.Nil(t, err)
//check nil obj
err = validator.Validate(nil)
assert.NotNil(t, err)
assert.EqualError(t, err, "schema validate failed: (root): Invalid type. Expected: object, given: null")
//plugin schema fail
consumer3 := &entity.Consumer{}
reqBody = `{
"username": "jack",
"plugins": {
"limit-count": {
"time_window": 60,
"rejected_code": 503,
"key": "remote_addr",
"policy": "local"
}
},
"desc": "test description"
}`
err = json.Unmarshal([]byte(reqBody), consumer3)
assert.Nil(t, err)
err = validator.Validate(consumer3)
assert.NotNil(t, err)
assert.EqualError(t, err, "schema validate failed: (root): count is required")
}
func TestAPISIXJsonSchemaValidator_checkUpstream(t *testing.T) {
validator, err := NewAPISIXJsonSchemaValidator("main.route")
assert.Nil(t, err)
// type:chash, hash_on: consumer, missing key, ok
route := &entity.Route{}
reqBody := `{
"id": "1",
"name": "route1",
"methods": ["GET"],
"upstream": {
"nodes": {
"127.0.0.1:8080": 1
},
"type": "chash",
"hash_on":"consumer"
},
"desc": "new route",
"uri": "/index.html"
}`
err = json.Unmarshal([]byte(reqBody), route)
assert.Nil(t, err)
err = validator.Validate(route)
assert.Nil(t, err)
// type:chash, hash_on: default(vars), missing key
route2 := &entity.Route{}
reqBody = `{
"id": "1",
"name": "route1",
"methods": ["GET"],
"upstream": {
"nodes": {
"127.0.0.1:8080": 1
},
"type": "chash"
},
"desc": "new route",
"uri": "/index.html"
}`
err = json.Unmarshal([]byte(reqBody), route2)
assert.Nil(t, err)
err = validator.Validate(route2)
assert.NotNil(t, err)
assert.EqualError(t, err, "missing key")
//type:chash, hash_on: header, missing key
route3 := &entity.Route{}
reqBody = `{
"id": "1",
"name": "route1",
"methods": ["GET"],
"upstream": {
"nodes": {
"127.0.0.1:8080": 1
},
"type": "chash",
"hash_on":"header"
},
"desc": "new route",
"uri": "/index.html"
}`
err = json.Unmarshal([]byte(reqBody), route3)
assert.Nil(t, err)
err = validator.Validate(route3)
assert.NotNil(t, err)
assert.EqualError(t, err, "missing key")
//type:chash, hash_on: cookie, missing key
route4 := &entity.Route{}
reqBody = `{
"id": "1",
"name": "route1",
"methods": ["GET"],
"upstream": {
"nodes": {
"127.0.0.1:8080": 1
},
"type": "chash",
"hash_on":"cookie"
},
"desc": "new route",
"uri": "/index.html"
}`
err = json.Unmarshal([]byte(reqBody), route4)
assert.Nil(t, err)
err = validator.Validate(route4)
assert.NotNil(t, err)
assert.EqualError(t, err, "missing key")
//type:chash, hash_on: vars, wrong key
route5 := &entity.Route{}
reqBody = `{
"id": "1",
"name": "route1",
"methods": ["GET"],
"upstream": {
"nodes": {
"127.0.0.1:8080": 1
},
"type": "chash",
"hash_on":"vars",
"key": "not_support"
},
"desc": "new route",
"uri": "/index.html"
}`
err = json.Unmarshal([]byte(reqBody), route5)
assert.Nil(t, err)
err = validator.Validate(route5)
assert.NotNil(t, err)
assert.EqualError(t, err, "schema validate failed: (root): Does not match pattern '^((uri|server_name|server_addr|request_uri|remote_port|remote_addr|query_string|host|hostname)|arg_[0-9a-zA-z_-]+)$'")
}
func TestAPISIXJsonSchemaValidator_Plugin(t *testing.T) {
validator, err := NewAPISIXJsonSchemaValidator("main.route")
assert.Nil(t, err)
// validate plugin's schema which has no `properties` or empty `properties`
route := &entity.Route{}
reqBody := `{
"id": "1",
"name": "route1",
"uri": "/hello",
"plugins": {
"prometheus": {
"disable": false
},
"key-auth": {
"disable": true
}
}
}`
err = json.Unmarshal([]byte(reqBody), route)
assert.Nil(t, err)
err = validator.Validate(route)
assert.Nil(t, err)
// validate plugin's schema which use `oneOf`
reqBody = `{
"id": "1",
"uri": "/hello",
"plugins": {
"ip-restriction": {
"blacklist": [
"127.0.0.0/24"
],
"disable": true
}
}
}`
err = json.Unmarshal([]byte(reqBody), route)
assert.Nil(t, err)
err = validator.Validate(route)
assert.Nil(t, err)
// validate plugin's schema with invalid type for `disable`
reqBody = `{
"id": "1",
"uri": "/hello",
"plugins": {
"ip-restriction": {
"blacklist": [
"127.0.0.0/24"
],
"_meta": {
"disable": 1
}
}
}
}`
err = json.Unmarshal([]byte(reqBody), route)
assert.Nil(t, err)
err = validator.Validate(route)
assert.Equal(t, fmt.Errorf("schema validate failed: _meta.disable: Invalid type. Expected: boolean, given: integer"), err)
}
func TestAPISIXJsonSchemaValidator_Route_checkRemoteAddr(t *testing.T) {
tests := []struct {
caseDesc string
giveContent string
wantNewErr error
wantValidateErr error
}{
{
caseDesc: "correct remote_addr",
giveContent: `{
"id": "1",
"name": "route1",
"uri": "/*",
"upstream": {
"nodes": [{
"host": "127.0.0.1",
"port": 8080,
"weight": 1
}],
"type": "roundrobin"
},
"remote_addr": "127.0.0.1"
}`,
},
{
caseDesc: "correct remote_addr (CIDR)",
giveContent: `{
"id": "1",
"name": "route1",
"uri": "/*",
"upstream": {
"nodes": [{
"host": "127.0.0.1",
"port": 8080,
"weight": 1
}],
"type": "roundrobin"
},
"remote_addr": "192.168.1.0/24"
}`,
},
{
caseDesc: "invalid remote_addr",
giveContent: `{
"id": "1",
"name": "route1",
"uri": "/*",
"upstream": {
"nodes": [{
"host": "127.0.0.1",
"port": 8080,
"weight": 1
}],
"type": "roundrobin"
},
"remote_addr": "127.0.0."
}`,
wantValidateErr: fmt.Errorf("schema validate failed: remote_addr: Must validate at least one schema (anyOf)\nremote_addr: Does not match format 'ipv4'"),
},
{
caseDesc: "correct remote_addrs",
giveContent: `{
"id": "1",
"name": "route1",
"uri": "/*",
"upstream": {
"nodes": [{
"host": "127.0.0.1",
"port": 8080,
"weight": 1
}],
"type": "roundrobin"
},
"remote_addrs": ["127.0.0.1", "192.0.0.0/8", "::1", "fe80::1/64"]
}`,
},
{
caseDesc: "invalid remote_addrs",
giveContent: `{
"id": "1",
"name": "route1",
"uri": "/*",
"upstream": {
"nodes": [{
"host": "127.0.0.1",
"port": 8080,
"weight": 1
}],
"type": "roundrobin"
},
"remote_addrs": ["127.0.0.", "192.0.0.0/128", "::1"]
}`,
wantValidateErr: fmt.Errorf("schema validate failed: remote_addrs.0: Must validate at least one schema (anyOf)\nremote_addrs.0: Does not match format 'ipv4'\nremote_addrs.1: Must validate at least one schema (anyOf)\nremote_addrs.1: Does not match format 'ipv4'"),
},
{
caseDesc: "invalid remote_addrs (an empty string item)",
giveContent: `{
"id": "1",
"name": "route1",
"uri": "/*",
"upstream": {
"nodes": [{
"host": "127.0.0.1",
"port": 8080,
"weight": 1
}],
"type": "roundrobin"
},
"remote_addrs": [""]
}`,
wantValidateErr: fmt.Errorf("schema validate failed: remote_addrs.0: Must validate at least one schema (anyOf)\nremote_addrs.0: Does not match format 'ipv4'"),
},
}
// todo: add a test case for "remote_addr": ""
for _, tc := range tests {
validator, err := NewAPISIXJsonSchemaValidator("main.route")
if err != nil {
assert.Equal(t, tc.wantNewErr, err, tc.caseDesc)
continue
}
route := &entity.Route{}
err = json.Unmarshal([]byte(tc.giveContent), route)
assert.Nil(t, err, tc.caseDesc)
err = validator.Validate(route)
if tc.wantValidateErr == nil {
assert.Equal(t, nil, err, tc.caseDesc)
continue
}
assert.Equal(t, tc.wantValidateErr, err, tc.caseDesc)
}
}
func TestAPISIXSchemaValidator_SystemConfig(t *testing.T) {
tests := []struct {
name string
givePath string
giveObj interface{}
wantNewErr bool
wantValidateErr bool
wantErrMessage string
}{
{
name: "new json schema validator failed",
givePath: "main.xxx",
wantNewErr: true,
wantErrMessage: "schema validate failed: schema not found, path: main.xxx",
},
{
name: "invalid configName (configName is empty)",
givePath: "main.system_config",
giveObj: &entity.SystemConfig{
Payload: map[string]interface{}{"a": 1},
},
wantValidateErr: true,
wantErrMessage: "schema validate failed: config_name: String length must be greater than or equal to 1\nconfig_name: Does not match pattern '^[a-zA-Z0-9_]+$'",
},
{
name: "invalid configName (configName do not match regex)",
givePath: "main.system_config",
giveObj: &entity.SystemConfig{
ConfigName: "1@2",
Payload: map[string]interface{}{"a": 1},
},
wantValidateErr: true,
wantErrMessage: "schema validate failed: config_name: Does not match pattern '^[a-zA-Z0-9_]+$'",
},
{
name: "invalid payload",
givePath: "main.system_config",
giveObj: &entity.SystemConfig{
ConfigName: "cc",
},
wantValidateErr: true,
wantErrMessage: "schema validate failed: (root): payload is required",
},
{
name: "validate should succeed",
givePath: "main.system_config",
giveObj: &entity.SystemConfig{
ConfigName: "aaa",
Payload: map[string]interface{}{"a": 1},
},
},
}
for _, tc := range tests {
validator, err := NewAPISIXSchemaValidator(tc.givePath)
if tc.wantNewErr {
assert.Error(t, err)
assert.Equal(t, tc.wantErrMessage, err.Error())
continue
}
assert.NoError(t, err)
assert.NotNil(t, validator)
req, err := json.Marshal(tc.giveObj)
assert.NoError(t, err)
err = validator.Validate(req)
if tc.wantValidateErr {
assert.Error(t, err)
assert.Equal(t, tc.wantErrMessage, err.Error())
continue
}
assert.NoError(t, err)
}
}
func TestAPISIXSchemaValidator_Validate(t *testing.T) {
validator, err := NewAPISIXSchemaValidator("main.consumer")
assert.Nil(t, err)
// normal config, should pass
reqBody := `{
"username": "jack",
"plugins": {
"limit-count": {
"count": 2,
"time_window": 60,
"rejected_code": 503,
"key": "remote_addr"
}
},
"desc": "test description"
}`
err = validator.Validate([]byte(reqBody))
assert.Nil(t, err)
}