[TASK] refactoring of filters (#114)
This commit is contained in:
parent
0d186677e4
commit
6b522c629c
@ -31,6 +31,9 @@ func TestReadConfig(t *testing.T) {
|
||||
"enable": false,
|
||||
"nodes_path": "/var/www/html/meshviewer/data/nodes.json",
|
||||
"graph_path": "/var/www/html/meshviewer/data/graph.json",
|
||||
"filter": map[string]interface{}{
|
||||
"no_owner": true,
|
||||
},
|
||||
}, meshviewer)
|
||||
|
||||
_, err = ReadConfigFile("testdata/config_invalid.toml")
|
||||
|
@ -46,12 +46,16 @@ offline_after = "10m"
|
||||
# For each output format there can be set different filters
|
||||
#[nodes.output.example.filter]
|
||||
#
|
||||
# Set to false, if you want the json files to contain the owner information
|
||||
# WARNING: if it is not set, it will publish contact information of other persons
|
||||
# Set to true, if you did not want the json files to contain the owner information
|
||||
#no_owner = true
|
||||
#
|
||||
# List of nodeids of nodes that should be filtered out, so they won't appear in output
|
||||
#blacklist = ["00112233445566", "1337f0badead"]
|
||||
#
|
||||
# List of site_codes of nodes that should be included in the output
|
||||
#sites = ["ffhb"]
|
||||
#
|
||||
# set has_location to true if you want to include only nodes that have geo-coordinates set
|
||||
# (setting this to false has no sensible effect, unless you'd want to hide nodes that have coordinates)
|
||||
#has_location = true
|
||||
@ -69,9 +73,11 @@ offline_after = "10m"
|
||||
enable = true
|
||||
path = "/var/www/html/meshviewer/data/meshviewer.json"
|
||||
|
||||
#[nodes.output.meshviewer-ffrgb.filter]
|
||||
#no_owner = false
|
||||
[nodes.output.meshviewer-ffrgb.filter]
|
||||
# WARNING: if it is not set, it will publish contact information of other persons
|
||||
no_owner = false
|
||||
#blacklist = ["00112233445566", "1337f0badead"]
|
||||
#sites = ["ffhb"]
|
||||
#has_location = true
|
||||
|
||||
#[nodes.output.meshviewer-ffrgb.filter.in_area]
|
||||
@ -96,8 +102,9 @@ nodes_path = "/var/www/html/meshviewer/data/nodes.json"
|
||||
# path where to store graph.json
|
||||
graph_path = "/var/www/html/meshviewer/data/graph.json"
|
||||
|
||||
#[nodes.output.meshviewer.filter]
|
||||
#no_owner = false
|
||||
[nodes.output.meshviewer.filter]
|
||||
# WARNING: if it is not set, it will publish contact information of other persons
|
||||
no_owner = true
|
||||
|
||||
|
||||
# definition for nodelist.json
|
||||
@ -105,8 +112,9 @@ graph_path = "/var/www/html/meshviewer/data/graph.json"
|
||||
enable = true
|
||||
path = "/var/www/html/meshviewer/data/nodelist.json"
|
||||
|
||||
#[nodes.output.nodelist.filter]
|
||||
#no_owner = false
|
||||
[nodes.output.nodelist.filter]
|
||||
# WARNING: if it is not set, it will publish contact information of other persons
|
||||
no_owner = true
|
||||
|
||||
|
||||
|
||||
|
@ -196,6 +196,7 @@ enable = true
|
||||
[nodes.output.example.filter]
|
||||
no_owner = true
|
||||
blacklist = ["00112233445566", "1337f0badead"]
|
||||
sites = ["ffhb"]
|
||||
has_location = true
|
||||
[nodes.output.example.filter.in_area]
|
||||
latitude_min = 34.30
|
||||
@ -222,6 +223,7 @@ For each output format there can be set different filters
|
||||
[nodes.output.example.filter]
|
||||
no_owner = true
|
||||
blacklist = ["00112233445566", "1337f0badead"]
|
||||
sites = ["ffhb"]
|
||||
has_location = true
|
||||
[nodes.output.example.filter.in_area]
|
||||
latitude_min = 34.30
|
||||
@ -235,6 +237,10 @@ longitude_max = 39.72
|
||||
### no_owner
|
||||
{% method %}
|
||||
Set to false, if you want the json files to contain the owner information
|
||||
|
||||
|
||||
**WARNING: if it is not set, it will publish contact information of other persons.**
|
||||
|
||||
{% sample lang="toml" %}
|
||||
```toml
|
||||
no_owner = true
|
||||
@ -252,6 +258,16 @@ blacklist = ["00112233445566", "1337f0badead"]
|
||||
{% endmethod %}
|
||||
|
||||
|
||||
### sites
|
||||
{% method %}
|
||||
List of site_codes of nodes that should be included in output
|
||||
{% sample lang="toml" %}
|
||||
```toml
|
||||
sites = ["ffhb"]
|
||||
```
|
||||
{% endmethod %}
|
||||
|
||||
|
||||
### has_location
|
||||
{% method %}
|
||||
set has_location to true if you want to include only nodes that have geo-coordinates set
|
||||
|
@ -1,44 +1,9 @@
|
||||
package all
|
||||
|
||||
import (
|
||||
"github.com/FreifunkBremen/yanic/runtime"
|
||||
_ "github.com/FreifunkBremen/yanic/output/filter/blacklist"
|
||||
_ "github.com/FreifunkBremen/yanic/output/filter/haslocation"
|
||||
_ "github.com/FreifunkBremen/yanic/output/filter/inarea"
|
||||
_ "github.com/FreifunkBremen/yanic/output/filter/noowner"
|
||||
_ "github.com/FreifunkBremen/yanic/output/filter/site"
|
||||
)
|
||||
|
||||
// Config Filter
|
||||
type filterConfig map[string]interface{}
|
||||
|
||||
type filterFunc func(*runtime.Node) *runtime.Node
|
||||
|
||||
func noFilter(node *runtime.Node) *runtime.Node {
|
||||
return node
|
||||
}
|
||||
|
||||
// Create Filter
|
||||
func (f filterConfig) filtering(nodesOrigin *runtime.Nodes) *runtime.Nodes {
|
||||
nodes := runtime.NewNodes(&runtime.NodesConfig{})
|
||||
filterfuncs := []filterFunc{
|
||||
f.HasLocation(),
|
||||
f.Blacklist(),
|
||||
f.InArea(),
|
||||
f.NoOwner(),
|
||||
}
|
||||
|
||||
nodesOrigin.Lock()
|
||||
defer nodesOrigin.Unlock()
|
||||
|
||||
for _, nodeOrigin := range nodesOrigin.List {
|
||||
//maybe cloning of this object is better?
|
||||
node := nodeOrigin
|
||||
for _, f := range filterfuncs {
|
||||
node = f(node)
|
||||
if node == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if node != nil {
|
||||
nodes.AddNode(node)
|
||||
}
|
||||
}
|
||||
return nodes
|
||||
}
|
||||
|
@ -1,24 +0,0 @@
|
||||
package all
|
||||
|
||||
import "github.com/FreifunkBremen/yanic/runtime"
|
||||
|
||||
func (f filterConfig) Blacklist() filterFunc {
|
||||
v, ok := f["blacklist"]
|
||||
if !ok {
|
||||
return noFilter
|
||||
}
|
||||
|
||||
list := make(map[string]interface{})
|
||||
for _, nodeid := range v.([]interface{}) {
|
||||
list[nodeid.(string)] = true
|
||||
}
|
||||
|
||||
return func(node *runtime.Node) *runtime.Node {
|
||||
if nodeinfo := node.Nodeinfo; nodeinfo != nil {
|
||||
if _, ok := list[nodeinfo.NodeID]; ok {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return node
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
package all
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/FreifunkBremen/yanic/data"
|
||||
"github.com/FreifunkBremen/yanic/runtime"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFilterBlacklist(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
var config filterConfig
|
||||
|
||||
config = map[string]interface{}{}
|
||||
|
||||
filterBlacklist := config.Blacklist()
|
||||
|
||||
n := filterBlacklist(&runtime.Node{Nodeinfo: &data.NodeInfo{}})
|
||||
assert.NotNil(n)
|
||||
|
||||
config["blacklist"] = []interface{}{"a", "c"}
|
||||
filterBlacklist = config.Blacklist()
|
||||
|
||||
n = filterBlacklist(&runtime.Node{Nodeinfo: &data.NodeInfo{NodeID: "a"}})
|
||||
assert.Nil(n)
|
||||
|
||||
n = filterBlacklist(&runtime.Node{Nodeinfo: &data.NodeInfo{}})
|
||||
assert.NotNil(n)
|
||||
|
||||
n = filterBlacklist(&runtime.Node{})
|
||||
assert.NotNil(n)
|
||||
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
package all
|
||||
|
||||
import "github.com/FreifunkBremen/yanic/runtime"
|
||||
|
||||
func (f filterConfig) HasLocation() filterFunc {
|
||||
withLocation, ok := f["has_location"].(bool)
|
||||
if !ok {
|
||||
return noFilter
|
||||
}
|
||||
return func(node *runtime.Node) *runtime.Node {
|
||||
if nodeinfo := node.Nodeinfo; nodeinfo != nil {
|
||||
if withLocation {
|
||||
if location := nodeinfo.Location; location != nil {
|
||||
return node
|
||||
}
|
||||
} else {
|
||||
if location := nodeinfo.Location; location == nil {
|
||||
return node
|
||||
}
|
||||
}
|
||||
} else if !withLocation {
|
||||
return node
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
package all
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/FreifunkBremen/yanic/data"
|
||||
"github.com/FreifunkBremen/yanic/runtime"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFilterHasLocation(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
var config filterConfig
|
||||
|
||||
config = map[string]interface{}{}
|
||||
|
||||
filterHasLocation := config.HasLocation()
|
||||
n := filterHasLocation(&runtime.Node{Nodeinfo: &data.NodeInfo{
|
||||
Location: &data.Location{},
|
||||
}})
|
||||
assert.NotNil(n)
|
||||
|
||||
config["has_location"] = true
|
||||
filterHasLocation = config.HasLocation()
|
||||
|
||||
n = filterHasLocation(&runtime.Node{Nodeinfo: &data.NodeInfo{
|
||||
Location: &data.Location{},
|
||||
}})
|
||||
assert.NotNil(n)
|
||||
|
||||
n = filterHasLocation(&runtime.Node{Nodeinfo: &data.NodeInfo{}})
|
||||
assert.Nil(n)
|
||||
|
||||
n = filterHasLocation(&runtime.Node{})
|
||||
assert.Nil(n)
|
||||
|
||||
config["has_location"] = false
|
||||
filterHasLocation = config.HasLocation()
|
||||
|
||||
n = filterHasLocation(&runtime.Node{Nodeinfo: &data.NodeInfo{
|
||||
Location: &data.Location{},
|
||||
}})
|
||||
assert.Nil(n)
|
||||
|
||||
n = filterHasLocation(&runtime.Node{Nodeinfo: &data.NodeInfo{}})
|
||||
assert.NotNil(n)
|
||||
|
||||
n = filterHasLocation(&runtime.Node{})
|
||||
assert.NotNil(n)
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
package all
|
||||
|
||||
import "github.com/FreifunkBremen/yanic/runtime"
|
||||
|
||||
type area struct {
|
||||
latitudeMin float64
|
||||
latitudeMax float64
|
||||
longitudeMin float64
|
||||
longitudeMax float64
|
||||
}
|
||||
|
||||
func (f filterConfig) InArea() filterFunc {
|
||||
if areaConfigInt, ok := f["in_area"]; ok {
|
||||
areaConfig := areaConfigInt.(map[string]interface{})
|
||||
a := area{}
|
||||
if v, ok := areaConfig["latitude_min"]; ok {
|
||||
a.latitudeMin = v.(float64)
|
||||
}
|
||||
if v, ok := areaConfig["latitude_max"]; ok {
|
||||
a.latitudeMax = v.(float64)
|
||||
}
|
||||
if v, ok := areaConfig["longitude_min"]; ok {
|
||||
a.longitudeMin = v.(float64)
|
||||
}
|
||||
if v, ok := areaConfig["longitude_max"]; ok {
|
||||
a.longitudeMax = v.(float64)
|
||||
}
|
||||
return func(node *runtime.Node) *runtime.Node {
|
||||
if nodeinfo := node.Nodeinfo; nodeinfo != nil {
|
||||
location := nodeinfo.Location
|
||||
if location == nil {
|
||||
return node
|
||||
}
|
||||
if location.Latitude >= a.latitudeMin && location.Latitude <= a.latitudeMax && location.Longitude >= a.longitudeMin && location.Longitude <= a.longitudeMax {
|
||||
return node
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return noFilter
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
package all
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/FreifunkBremen/yanic/data"
|
||||
"github.com/FreifunkBremen/yanic/runtime"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFilterInArea(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
var config filterConfig
|
||||
areaConfig := map[string]interface{}{
|
||||
"latitude_min": 3.0,
|
||||
"latitude_max": 5.0,
|
||||
"longitude_min": 10.0,
|
||||
"longitude_max": 12.0,
|
||||
}
|
||||
config = map[string]interface{}{}
|
||||
|
||||
filterLocationInArea := config.InArea()
|
||||
n := filterLocationInArea(&runtime.Node{Nodeinfo: &data.NodeInfo{
|
||||
Location: &data.Location{Latitude: 4.0, Longitude: 11.0},
|
||||
}})
|
||||
assert.NotNil(n)
|
||||
|
||||
config["in_area"] = areaConfig
|
||||
filterLocationInArea = config.InArea()
|
||||
|
||||
// drop area without nodeinfo
|
||||
n = filterLocationInArea(&runtime.Node{})
|
||||
assert.Nil(n)
|
||||
|
||||
// keep without location
|
||||
n = filterLocationInArea(&runtime.Node{Nodeinfo: &data.NodeInfo{}})
|
||||
assert.NotNil(n)
|
||||
|
||||
// zeros not in area (0, 0)
|
||||
n = filterLocationInArea(&runtime.Node{Nodeinfo: &data.NodeInfo{
|
||||
Location: &data.Location{},
|
||||
}})
|
||||
assert.Nil(n)
|
||||
|
||||
// in area
|
||||
n = filterLocationInArea(&runtime.Node{Nodeinfo: &data.NodeInfo{
|
||||
Location: &data.Location{Latitude: 4.0, Longitude: 11.0},
|
||||
}})
|
||||
assert.NotNil(n)
|
||||
|
||||
n = filterLocationInArea(&runtime.Node{Nodeinfo: &data.NodeInfo{
|
||||
Location: &data.Location{Latitude: 4.0, Longitude: 13.0},
|
||||
}})
|
||||
assert.Nil(n)
|
||||
|
||||
n = filterLocationInArea(&runtime.Node{Nodeinfo: &data.NodeInfo{
|
||||
Location: &data.Location{Latitude: 6.0, Longitude: 11.0},
|
||||
}})
|
||||
assert.Nil(n)
|
||||
|
||||
n = filterLocationInArea(&runtime.Node{Nodeinfo: &data.NodeInfo{
|
||||
Location: &data.Location{Latitude: 1.0, Longitude: 2.0},
|
||||
}})
|
||||
assert.Nil(n)
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
package all
|
||||
|
||||
import (
|
||||
"github.com/FreifunkBremen/yanic/data"
|
||||
"github.com/FreifunkBremen/yanic/runtime"
|
||||
)
|
||||
|
||||
func (f filterConfig) NoOwner() filterFunc {
|
||||
if v, ok := f["no_owner"]; ok && v.(bool) == false {
|
||||
return noFilter
|
||||
}
|
||||
return func(node *runtime.Node) *runtime.Node {
|
||||
if nodeinfo := node.Nodeinfo; nodeinfo != nil {
|
||||
return &runtime.Node{
|
||||
Address: node.Address,
|
||||
Firstseen: node.Firstseen,
|
||||
Lastseen: node.Lastseen,
|
||||
Online: node.Online,
|
||||
Statistics: node.Statistics,
|
||||
Nodeinfo: &data.NodeInfo{
|
||||
NodeID: nodeinfo.NodeID,
|
||||
Network: nodeinfo.Network,
|
||||
System: nodeinfo.System,
|
||||
Owner: nil,
|
||||
Hostname: nodeinfo.Hostname,
|
||||
Location: nodeinfo.Location,
|
||||
Software: nodeinfo.Software,
|
||||
Hardware: nodeinfo.Hardware,
|
||||
VPN: nodeinfo.VPN,
|
||||
Wireless: nodeinfo.Wireless,
|
||||
},
|
||||
Neighbours: node.Neighbours,
|
||||
}
|
||||
}
|
||||
return node
|
||||
}
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
package all
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/FreifunkBremen/yanic/data"
|
||||
"github.com/FreifunkBremen/yanic/runtime"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFilterNoOwner(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
var config filterConfig
|
||||
|
||||
config = map[string]interface{}{}
|
||||
|
||||
filterNoOwner := config.NoOwner()
|
||||
n := filterNoOwner(&runtime.Node{Nodeinfo: &data.NodeInfo{
|
||||
Owner: &data.Owner{
|
||||
Contact: "blub",
|
||||
},
|
||||
}})
|
||||
assert.NotNil(n)
|
||||
assert.Nil(n.Nodeinfo.Owner)
|
||||
|
||||
n = filterNoOwner(&runtime.Node{})
|
||||
assert.NotNil(n)
|
||||
|
||||
config["no_owner"] = true
|
||||
filterNoOwner = config.NoOwner()
|
||||
n = filterNoOwner(&runtime.Node{Nodeinfo: &data.NodeInfo{
|
||||
Owner: &data.Owner{
|
||||
Contact: "blub",
|
||||
},
|
||||
}})
|
||||
assert.NotNil(n)
|
||||
assert.Nil(n.Nodeinfo.Owner)
|
||||
|
||||
config["no_owner"] = false
|
||||
filterNoOwner = config.NoOwner()
|
||||
|
||||
n = filterNoOwner(&runtime.Node{Nodeinfo: &data.NodeInfo{
|
||||
Owner: &data.Owner{
|
||||
Contact: "blub",
|
||||
},
|
||||
}})
|
||||
assert.NotNil(n)
|
||||
assert.NotNil(n.Nodeinfo.Owner)
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
package all
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/FreifunkBremen/yanic/data"
|
||||
"github.com/FreifunkBremen/yanic/runtime"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFilter(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
// filtered - do not run all
|
||||
nodes := &runtime.Nodes{
|
||||
List: map[string]*runtime.Node{
|
||||
"a": {
|
||||
Nodeinfo: &data.NodeInfo{NodeID: "a"},
|
||||
},
|
||||
},
|
||||
}
|
||||
config := filterConfig{
|
||||
"has_location": true,
|
||||
}
|
||||
nodes = config.filtering(nodes)
|
||||
assert.Len(nodes.List, 0)
|
||||
|
||||
// run to end
|
||||
nodes = &runtime.Nodes{
|
||||
List: map[string]*runtime.Node{
|
||||
"a": {
|
||||
Nodeinfo: &data.NodeInfo{NodeID: "a"},
|
||||
},
|
||||
},
|
||||
}
|
||||
config = filterConfig{
|
||||
"has_location": false,
|
||||
}
|
||||
nodes = config.filtering(nodes)
|
||||
assert.Len(nodes.List, 1)
|
||||
}
|
@ -5,18 +5,19 @@ import (
|
||||
"log"
|
||||
|
||||
"github.com/FreifunkBremen/yanic/output"
|
||||
"github.com/FreifunkBremen/yanic/output/filter"
|
||||
"github.com/FreifunkBremen/yanic/runtime"
|
||||
)
|
||||
|
||||
type Output struct {
|
||||
output.Output
|
||||
list map[int]output.Output
|
||||
filter map[int]filterConfig
|
||||
outputFilter map[int]filter.Set
|
||||
}
|
||||
|
||||
func Register(configuration map[string]interface{}) (output.Output, error) {
|
||||
list := make(map[int]output.Output)
|
||||
filter := make(map[int]filterConfig)
|
||||
outputFilter := make(map[int]filter.Set)
|
||||
i := 1
|
||||
allOutputs := configuration
|
||||
for outputType, outputRegister := range output.Adapters {
|
||||
@ -44,25 +45,26 @@ func Register(configuration map[string]interface{}) (output.Output, error) {
|
||||
if output == nil {
|
||||
continue
|
||||
}
|
||||
list[i] = output
|
||||
var errs []error
|
||||
var filterSet filter.Set
|
||||
if c := config["filter"]; c != nil {
|
||||
filter[i] = config["filter"].(map[string]interface{})
|
||||
if filterConf, ok := c.(map[string]interface{}); ok {
|
||||
filterSet, errs = filter.New(filterConf)
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
return nil, fmt.Errorf("filter configuration errors: %v", errs)
|
||||
}
|
||||
outputFilter[i] = filterSet
|
||||
}
|
||||
list[i] = output
|
||||
i++
|
||||
}
|
||||
}
|
||||
return &Output{list: list, filter: filter}, nil
|
||||
return &Output{list: list, outputFilter: outputFilter}, nil
|
||||
}
|
||||
|
||||
func (o *Output) Save(nodes *runtime.Nodes) {
|
||||
for i, item := range o.list {
|
||||
var filteredNodes *runtime.Nodes
|
||||
if config := o.filter[i]; config != nil {
|
||||
filteredNodes = config.filtering(nodes)
|
||||
} else {
|
||||
filteredNodes = filterConfig{}.filtering(nodes)
|
||||
}
|
||||
|
||||
item.Save(filteredNodes)
|
||||
item.Save(o.outputFilter[i].Apply(nodes))
|
||||
}
|
||||
}
|
||||
|
@ -82,16 +82,40 @@ func TestStart(t *testing.T) {
|
||||
allOutput.Save(nodes)
|
||||
assert.Equal(3, globalOutput.Get())
|
||||
|
||||
// wrong format - map
|
||||
_, err = Register(map[string]interface{}{
|
||||
"e": []map[string]interface{}{
|
||||
{},
|
||||
"e": []interface{}{
|
||||
false,
|
||||
},
|
||||
})
|
||||
assert.Error(err)
|
||||
|
||||
// wrong format
|
||||
// wrong format - array
|
||||
_, err = Register(map[string]interface{}{
|
||||
"e": true,
|
||||
})
|
||||
assert.Error(err)
|
||||
|
||||
// output error
|
||||
_, err = Register(map[string]interface{}{
|
||||
"e": []interface{}{
|
||||
map[string]interface{}{
|
||||
"enable": true,
|
||||
},
|
||||
},
|
||||
})
|
||||
assert.Error(err)
|
||||
|
||||
// output error invalid config of filter
|
||||
_, err = Register(map[string]interface{}{
|
||||
"a": []interface{}{
|
||||
map[string]interface{}{
|
||||
"enable": true,
|
||||
"filter": map[string]interface{}{
|
||||
"blacklist": true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
assert.Error(err)
|
||||
}
|
||||
|
36
output/filter/blacklist/blacklist.go
Normal file
36
output/filter/blacklist/blacklist.go
Normal file
@ -0,0 +1,36 @@
|
||||
package blacklist
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/FreifunkBremen/yanic/output/filter"
|
||||
"github.com/FreifunkBremen/yanic/runtime"
|
||||
)
|
||||
|
||||
type blacklist map[string]interface{}
|
||||
|
||||
func init() {
|
||||
filter.Register("blacklist", build)
|
||||
}
|
||||
|
||||
func build(config interface{}) (filter.Filter, error) {
|
||||
values, ok := config.([]string)
|
||||
if !ok {
|
||||
return nil, errors.New("invalid configuration, array of strings expected")
|
||||
}
|
||||
|
||||
list := make(blacklist)
|
||||
for _, nodeid := range values {
|
||||
list[nodeid] = struct{}{}
|
||||
}
|
||||
return &list, nil
|
||||
}
|
||||
|
||||
func (list blacklist) Apply(node *runtime.Node) *runtime.Node {
|
||||
if nodeinfo := node.Nodeinfo; nodeinfo != nil {
|
||||
if _, ok := list[nodeinfo.NodeID]; ok {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return node
|
||||
}
|
38
output/filter/blacklist/blacklist_test.go
Normal file
38
output/filter/blacklist/blacklist_test.go
Normal file
@ -0,0 +1,38 @@
|
||||
package blacklist
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/FreifunkBremen/yanic/data"
|
||||
"github.com/FreifunkBremen/yanic/runtime"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFilterBlacklist(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
// invalid config
|
||||
filter, err := build(3)
|
||||
assert.Error(err)
|
||||
|
||||
// tests with empty list
|
||||
filter, err = build([]string{})
|
||||
|
||||
// keep node without nodeid
|
||||
n := filter.Apply(&runtime.Node{Nodeinfo: &data.NodeInfo{}})
|
||||
assert.NotNil(n)
|
||||
|
||||
// tests with blacklist
|
||||
filter, _ = build([]string{"a", "c"})
|
||||
|
||||
// blacklist contains node with nodeid -> drop it
|
||||
n = filter.Apply(&runtime.Node{Nodeinfo: &data.NodeInfo{NodeID: "a"}})
|
||||
assert.Nil(n)
|
||||
|
||||
// blacklist does not contains node without nodeid -> keep it
|
||||
n = filter.Apply(&runtime.Node{Nodeinfo: &data.NodeInfo{}})
|
||||
assert.NotNil(n)
|
||||
|
||||
n = filter.Apply(&runtime.Node{})
|
||||
assert.NotNil(n)
|
||||
}
|
73
output/filter/filter.go
Normal file
73
output/filter/filter.go
Normal file
@ -0,0 +1,73 @@
|
||||
package filter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/FreifunkBremen/yanic/runtime"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// factory function for building a filter
|
||||
// may return nil if the filter never applies
|
||||
type factory func(interface{}) (Filter, error)
|
||||
|
||||
// Filter is a filter instance
|
||||
type Filter interface {
|
||||
Apply(*runtime.Node) *runtime.Node
|
||||
}
|
||||
|
||||
// Set is a list of configured filters
|
||||
type Set []Filter
|
||||
|
||||
var filters = make(map[string]factory)
|
||||
|
||||
// Register registers a new filter
|
||||
func Register(name string, f factory) {
|
||||
if _, ok := filters[name]; ok {
|
||||
panic("already registered: " + name)
|
||||
}
|
||||
filters[name] = f
|
||||
}
|
||||
|
||||
// New returns and initializes a set of filters
|
||||
func New(configs map[string]interface{}) (set Set, errs []error) {
|
||||
for name, config := range configs {
|
||||
if config == nil {
|
||||
return
|
||||
}
|
||||
|
||||
f, _ := filters[name]
|
||||
if f == nil {
|
||||
errs = append(errs, fmt.Errorf("unknown filter: %s", name))
|
||||
} else if filter, err := f(config); err != nil {
|
||||
errs = append(errs, errors.Wrapf(err, "unable to initialize filter %s", name))
|
||||
} else if filter != nil {
|
||||
set = append(set, filter)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Apply applies the filter set to the given node list and returns a new node list
|
||||
func (set Set) Apply(nodesOrigin *runtime.Nodes) *runtime.Nodes {
|
||||
nodes := runtime.NewNodes(&runtime.NodesConfig{})
|
||||
|
||||
nodesOrigin.Lock()
|
||||
defer nodesOrigin.Unlock()
|
||||
|
||||
for _, nodeOrigin := range nodesOrigin.List {
|
||||
//maybe cloning of this object is better?
|
||||
node := nodeOrigin
|
||||
for _, filter := range set {
|
||||
node = filter.Apply(node)
|
||||
if node == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if node != nil {
|
||||
nodes.AddNode(node)
|
||||
}
|
||||
}
|
||||
return nodes
|
||||
}
|
96
output/filter/filter_test.go
Normal file
96
output/filter/filter_test.go
Normal file
@ -0,0 +1,96 @@
|
||||
package filter
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/FreifunkBremen/yanic/data"
|
||||
"github.com/FreifunkBremen/yanic/runtime"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type filterBool struct{ bool }
|
||||
|
||||
func (f filterBool) Apply(node *runtime.Node) *runtime.Node {
|
||||
if f.bool {
|
||||
return node
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func build(v interface{}) (Filter, error) {
|
||||
if config, ok := v.(bool); ok {
|
||||
return &filterBool{config}, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func buildNil(v interface{}) (Filter, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func buildError(v interface{}) (Filter, error) {
|
||||
if v != nil {
|
||||
return nil, errors.New("some errors")
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func TestFilter(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
Register("test_nil", buildNil)
|
||||
Register("test_err", buildError)
|
||||
Register("test", build)
|
||||
|
||||
// filter still exists
|
||||
filter, err := New(map[string]interface{}{
|
||||
"adsa": true,
|
||||
})
|
||||
assert.Len(err, 1)
|
||||
assert.Nil(filter)
|
||||
|
||||
// no filter
|
||||
filter, err = New(map[string]interface{}{
|
||||
"test_nil": 3,
|
||||
})
|
||||
assert.Len(err, 0)
|
||||
assert.Len(filter, 0)
|
||||
|
||||
// filter error
|
||||
filter, err = New(map[string]interface{}{
|
||||
"test_err": false,
|
||||
})
|
||||
assert.Len(err, 1)
|
||||
assert.Nil(filter)
|
||||
|
||||
// filter a node
|
||||
nodes := &runtime.Nodes{
|
||||
List: map[string]*runtime.Node{
|
||||
"a": {
|
||||
Nodeinfo: &data.NodeInfo{NodeID: "a"},
|
||||
},
|
||||
},
|
||||
}
|
||||
filter, err = New(map[string]interface{}{
|
||||
"test": false,
|
||||
})
|
||||
assert.Len(err, 0)
|
||||
nodes = filter.Apply(nodes)
|
||||
assert.Len(nodes.List, 0)
|
||||
|
||||
// keep a node
|
||||
nodes = &runtime.Nodes{
|
||||
List: map[string]*runtime.Node{
|
||||
"a": {
|
||||
Nodeinfo: &data.NodeInfo{NodeID: "a"},
|
||||
},
|
||||
},
|
||||
}
|
||||
filter, err = New(map[string]interface{}{
|
||||
"test": true,
|
||||
})
|
||||
assert.Len(err, 0)
|
||||
nodes = filter.Apply(nodes)
|
||||
assert.Len(nodes.List, 1)
|
||||
}
|
40
output/filter/haslocation/haslocation.go
Normal file
40
output/filter/haslocation/haslocation.go
Normal file
@ -0,0 +1,40 @@
|
||||
package haslocation
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/FreifunkBremen/yanic/output/filter"
|
||||
"github.com/FreifunkBremen/yanic/runtime"
|
||||
)
|
||||
|
||||
type haslocation struct {
|
||||
has bool
|
||||
}
|
||||
|
||||
func init() {
|
||||
filter.Register("has_location", build)
|
||||
}
|
||||
|
||||
func build(config interface{}) (filter.Filter, error) {
|
||||
if value, ok := config.(bool); ok {
|
||||
return &haslocation{has: value}, nil
|
||||
}
|
||||
return nil, errors.New("invalid configuration, bool expected")
|
||||
}
|
||||
|
||||
func (h *haslocation) Apply(node *runtime.Node) *runtime.Node {
|
||||
if nodeinfo := node.Nodeinfo; nodeinfo != nil {
|
||||
if h.has {
|
||||
if location := nodeinfo.Location; location != nil {
|
||||
return node
|
||||
}
|
||||
} else {
|
||||
if location := nodeinfo.Location; location == nil {
|
||||
return node
|
||||
}
|
||||
}
|
||||
} else if !h.has {
|
||||
return node
|
||||
}
|
||||
return nil
|
||||
}
|
53
output/filter/haslocation/haslocation_test.go
Normal file
53
output/filter/haslocation/haslocation_test.go
Normal file
@ -0,0 +1,53 @@
|
||||
package haslocation
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/FreifunkBremen/yanic/data"
|
||||
"github.com/FreifunkBremen/yanic/runtime"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFilterHasLocation(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
// invalid config
|
||||
filter, err := build(3)
|
||||
assert.Error(err)
|
||||
|
||||
// test to drop nodes without location
|
||||
filter, err = build(true)
|
||||
assert.NoError(err)
|
||||
|
||||
// node has location (with 0,0) -> keep it
|
||||
n := filter.Apply(&runtime.Node{Nodeinfo: &data.NodeInfo{
|
||||
Location: &data.Location{},
|
||||
}})
|
||||
assert.NotNil(n)
|
||||
|
||||
// node without location has no location -> drop it
|
||||
n = filter.Apply(&runtime.Node{Nodeinfo: &data.NodeInfo{}})
|
||||
assert.Nil(n)
|
||||
|
||||
// node without nodeinfo has no location -> drop it
|
||||
n = filter.Apply(&runtime.Node{})
|
||||
assert.Nil(n)
|
||||
|
||||
// test to drop nodes without location
|
||||
filter, err = build(false)
|
||||
assert.NoError(err)
|
||||
|
||||
// node has location (with 0,0) -> drop it
|
||||
n = filter.Apply(&runtime.Node{Nodeinfo: &data.NodeInfo{
|
||||
Location: &data.Location{},
|
||||
}})
|
||||
assert.Nil(n)
|
||||
|
||||
// node without location has no location -> keep it
|
||||
n = filter.Apply(&runtime.Node{Nodeinfo: &data.NodeInfo{}})
|
||||
assert.NotNil(n)
|
||||
|
||||
// node without nodeinfo has no location -> keep it
|
||||
n = filter.Apply(&runtime.Node{})
|
||||
assert.NotNil(n)
|
||||
}
|
65
output/filter/inarea/inarea.go
Normal file
65
output/filter/inarea/inarea.go
Normal file
@ -0,0 +1,65 @@
|
||||
package inarea
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/FreifunkBremen/yanic/output/filter"
|
||||
"github.com/FreifunkBremen/yanic/runtime"
|
||||
)
|
||||
|
||||
type area struct {
|
||||
latitudeMin float64
|
||||
latitudeMax float64
|
||||
longitudeMin float64
|
||||
longitudeMax float64
|
||||
}
|
||||
|
||||
func init() {
|
||||
filter.Register("in_area", build)
|
||||
}
|
||||
|
||||
func build(config interface{}) (filter.Filter, error) {
|
||||
values, ok := config.(map[string]interface{})
|
||||
if !ok {
|
||||
return nil, errors.New("invalid configuration, map expected")
|
||||
}
|
||||
|
||||
a := area{}
|
||||
if v, ok := values["latitude_min"]; ok {
|
||||
a.latitudeMin = v.(float64)
|
||||
}
|
||||
if v, ok := values["latitude_max"]; ok {
|
||||
a.latitudeMax = v.(float64)
|
||||
}
|
||||
if v, ok := values["longitude_min"]; ok {
|
||||
a.longitudeMin = v.(float64)
|
||||
}
|
||||
if v, ok := values["longitude_max"]; ok {
|
||||
a.longitudeMax = v.(float64)
|
||||
}
|
||||
|
||||
if a.latitudeMin >= a.latitudeMax {
|
||||
return nil, errors.New("invalid latitude: max is bigger then min")
|
||||
}
|
||||
if a.longitudeMin >= a.longitudeMax {
|
||||
return nil, errors.New("invalid longitude: max is bigger then min")
|
||||
}
|
||||
|
||||
// TODO bessere Fehlerbehandlung!
|
||||
|
||||
return &a, nil
|
||||
}
|
||||
|
||||
func (a *area) Apply(node *runtime.Node) *runtime.Node {
|
||||
if nodeinfo := node.Nodeinfo; nodeinfo != nil {
|
||||
location := nodeinfo.Location
|
||||
if location == nil {
|
||||
return node
|
||||
}
|
||||
if location.Latitude >= a.latitudeMin && location.Latitude <= a.latitudeMax && location.Longitude >= a.longitudeMin && location.Longitude <= a.longitudeMax {
|
||||
return node
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return node
|
||||
}
|
86
output/filter/inarea/inarea_test.go
Normal file
86
output/filter/inarea/inarea_test.go
Normal file
@ -0,0 +1,86 @@
|
||||
package inarea
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/FreifunkBremen/yanic/data"
|
||||
"github.com/FreifunkBremen/yanic/runtime"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFilterInArea(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
filter, _ := build(map[string]interface{}{
|
||||
"latitude_min": 3.0,
|
||||
"latitude_max": 5.0,
|
||||
"longitude_min": 10.0,
|
||||
"longitude_max": 12.0,
|
||||
})
|
||||
|
||||
n := filter.Apply(&runtime.Node{Nodeinfo: &data.NodeInfo{
|
||||
Location: &data.Location{Latitude: 4.0, Longitude: 11.0},
|
||||
}})
|
||||
assert.NotNil(n)
|
||||
|
||||
// keep without nodeinfo -> use has_location for it
|
||||
n = filter.Apply(&runtime.Node{})
|
||||
assert.NotNil(n)
|
||||
|
||||
// keep without location -> use has_location for it
|
||||
n = filter.Apply(&runtime.Node{Nodeinfo: &data.NodeInfo{}})
|
||||
assert.NotNil(n)
|
||||
|
||||
// zeros not in area (0, 0)
|
||||
n = filter.Apply(&runtime.Node{Nodeinfo: &data.NodeInfo{
|
||||
Location: &data.Location{},
|
||||
}})
|
||||
assert.Nil(n)
|
||||
|
||||
// in area
|
||||
n = filter.Apply(&runtime.Node{Nodeinfo: &data.NodeInfo{
|
||||
Location: &data.Location{Latitude: 4.0, Longitude: 11.0},
|
||||
}})
|
||||
assert.NotNil(n)
|
||||
|
||||
// over max longitude -> dropped
|
||||
n = filter.Apply(&runtime.Node{Nodeinfo: &data.NodeInfo{
|
||||
Location: &data.Location{Latitude: 4.0, Longitude: 13.0},
|
||||
}})
|
||||
assert.Nil(n)
|
||||
|
||||
// over max latitude -> dropped
|
||||
n = filter.Apply(&runtime.Node{Nodeinfo: &data.NodeInfo{
|
||||
Location: &data.Location{Latitude: 6.0, Longitude: 11.0},
|
||||
}})
|
||||
assert.Nil(n)
|
||||
|
||||
// lower then mix latitde -> dropped
|
||||
n = filter.Apply(&runtime.Node{Nodeinfo: &data.NodeInfo{
|
||||
Location: &data.Location{Latitude: 1.0, Longitude: 2.0},
|
||||
}})
|
||||
assert.Nil(n)
|
||||
|
||||
// invalid config format
|
||||
_, err := build(true)
|
||||
assert.Error(err)
|
||||
|
||||
// invalid config latitude switched max and min
|
||||
_, err = build(map[string]interface{}{
|
||||
"latitude_min": 5.0,
|
||||
"latitude_max": 3.0,
|
||||
"longitude_min": 10.0,
|
||||
"longitude_max": 12.0,
|
||||
})
|
||||
assert.Error(err)
|
||||
|
||||
// invalid config longitude switched max and min
|
||||
_, err = build(map[string]interface{}{
|
||||
"latitude_min": 3.0,
|
||||
"latitude_max": 5.0,
|
||||
"longitude_min": 15.0,
|
||||
"longitude_max": 10.0,
|
||||
})
|
||||
assert.Error(err)
|
||||
|
||||
}
|
48
output/filter/noowner/noowner.go
Normal file
48
output/filter/noowner/noowner.go
Normal file
@ -0,0 +1,48 @@
|
||||
package noowner
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/FreifunkBremen/yanic/data"
|
||||
"github.com/FreifunkBremen/yanic/output/filter"
|
||||
"github.com/FreifunkBremen/yanic/runtime"
|
||||
)
|
||||
|
||||
type noowner struct{ has bool }
|
||||
|
||||
func init() {
|
||||
filter.Register("noowner", build)
|
||||
}
|
||||
|
||||
func build(config interface{}) (filter.Filter, error) {
|
||||
if value, ok := config.(bool); ok {
|
||||
return &noowner{has: value}, nil
|
||||
}
|
||||
return nil, errors.New("invalid configuration, boolean expected")
|
||||
}
|
||||
|
||||
func (no *noowner) Apply(node *runtime.Node) *runtime.Node {
|
||||
if nodeinfo := node.Nodeinfo; nodeinfo != nil && no.has {
|
||||
node = &runtime.Node{
|
||||
Address: node.Address,
|
||||
Firstseen: node.Firstseen,
|
||||
Lastseen: node.Lastseen,
|
||||
Online: node.Online,
|
||||
Statistics: node.Statistics,
|
||||
Nodeinfo: &data.NodeInfo{
|
||||
NodeID: nodeinfo.NodeID,
|
||||
Network: nodeinfo.Network,
|
||||
System: nodeinfo.System,
|
||||
Owner: nil,
|
||||
Hostname: nodeinfo.Hostname,
|
||||
Location: nodeinfo.Location,
|
||||
Software: nodeinfo.Software,
|
||||
Hardware: nodeinfo.Hardware,
|
||||
VPN: nodeinfo.VPN,
|
||||
Wireless: nodeinfo.Wireless,
|
||||
},
|
||||
Neighbours: node.Neighbours,
|
||||
}
|
||||
}
|
||||
return node
|
||||
}
|
39
output/filter/noowner/noowner_test.go
Normal file
39
output/filter/noowner/noowner_test.go
Normal file
@ -0,0 +1,39 @@
|
||||
package noowner
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/FreifunkBremen/yanic/data"
|
||||
"github.com/FreifunkBremen/yanic/runtime"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFilter(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
// invalid config
|
||||
filter, err := build("nope")
|
||||
assert.Error(err)
|
||||
|
||||
// delete owner by configuration
|
||||
filter, _ = build(true)
|
||||
n := filter.Apply(&runtime.Node{Nodeinfo: &data.NodeInfo{
|
||||
Owner: &data.Owner{
|
||||
Contact: "blub",
|
||||
},
|
||||
}})
|
||||
|
||||
assert.NotNil(n)
|
||||
assert.Nil(n.Nodeinfo.Owner)
|
||||
|
||||
// keep owner configuration
|
||||
filter, _ = build(false)
|
||||
n = filter.Apply(&runtime.Node{Nodeinfo: &data.NodeInfo{
|
||||
Owner: &data.Owner{
|
||||
Contact: "blub",
|
||||
},
|
||||
}})
|
||||
|
||||
assert.NotNil(n)
|
||||
assert.NotNil(n.Nodeinfo.Owner)
|
||||
}
|
36
output/filter/site/site.go
Normal file
36
output/filter/site/site.go
Normal file
@ -0,0 +1,36 @@
|
||||
package site
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/FreifunkBremen/yanic/output/filter"
|
||||
"github.com/FreifunkBremen/yanic/runtime"
|
||||
)
|
||||
|
||||
type sites map[string]interface{}
|
||||
|
||||
func init() {
|
||||
filter.Register("sites", build)
|
||||
}
|
||||
|
||||
func build(config interface{}) (filter.Filter, error) {
|
||||
values, ok := config.([]string)
|
||||
if !ok {
|
||||
return nil, errors.New("invalid configuration, array of strings expected")
|
||||
}
|
||||
|
||||
list := make(sites)
|
||||
for _, nodeid := range values {
|
||||
list[nodeid] = struct{}{}
|
||||
}
|
||||
return &list, nil
|
||||
}
|
||||
|
||||
func (list sites) Apply(node *runtime.Node) *runtime.Node {
|
||||
if nodeinfo := node.Nodeinfo; nodeinfo != nil {
|
||||
if _, ok := list[nodeinfo.System.SiteCode]; ok {
|
||||
return node
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
32
output/filter/site/site_test.go
Normal file
32
output/filter/site/site_test.go
Normal file
@ -0,0 +1,32 @@
|
||||
package site
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/FreifunkBremen/yanic/data"
|
||||
"github.com/FreifunkBremen/yanic/runtime"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFilterSite(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
// invalid config
|
||||
filter, err := build("ffhb")
|
||||
assert.Error(err)
|
||||
|
||||
filter, err = build([]string{"ffhb"})
|
||||
assert.NoError(err)
|
||||
|
||||
// wronge node
|
||||
n := filter.Apply(&runtime.Node{Nodeinfo: &data.NodeInfo{System: data.System{SiteCode: "ffxx"}}})
|
||||
assert.Nil(n)
|
||||
|
||||
// right node
|
||||
n = filter.Apply(&runtime.Node{Nodeinfo: &data.NodeInfo{System: data.System{SiteCode: "ffhb"}}})
|
||||
assert.NotNil(n)
|
||||
|
||||
// node without data -> wrong node
|
||||
n = filter.Apply(&runtime.Node{})
|
||||
assert.Nil(n)
|
||||
}
|
Loading…
Reference in New Issue
Block a user