2016-12-28 01:45:11 +00:00
|
|
|
// Copyright 2016 clair authors
|
2016-01-19 20:16:45 +00:00
|
|
|
//
|
|
|
|
// 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.
|
|
|
|
|
2015-12-28 20:03:29 +00:00
|
|
|
package pgsql
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"testing"
|
|
|
|
|
2016-05-02 22:33:03 +00:00
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
|
2015-12-28 20:03:29 +00:00
|
|
|
"github.com/coreos/clair/database"
|
2017-01-03 21:00:20 +00:00
|
|
|
"github.com/coreos/clair/ext/versionfmt/dpkg"
|
2016-01-20 18:57:51 +00:00
|
|
|
cerrors "github.com/coreos/clair/utils/errors"
|
2015-12-28 20:03:29 +00:00
|
|
|
"github.com/coreos/clair/utils/types"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestFindLayer(t *testing.T) {
|
2016-05-02 22:33:03 +00:00
|
|
|
datastore, err := openDatabaseForTest("FindLayer", true)
|
2015-12-28 20:03:29 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Error(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer datastore.Close()
|
|
|
|
|
|
|
|
// Layer-0: no parent, no namespace, no feature, no vulnerability
|
|
|
|
layer, err := datastore.FindLayer("layer-0", false, false)
|
|
|
|
if assert.Nil(t, err) && assert.NotNil(t, layer) {
|
|
|
|
assert.Equal(t, "layer-0", layer.Name)
|
|
|
|
assert.Nil(t, layer.Namespace)
|
|
|
|
assert.Nil(t, layer.Parent)
|
|
|
|
assert.Equal(t, 1, layer.EngineVersion)
|
|
|
|
assert.Len(t, layer.Features, 0)
|
|
|
|
}
|
|
|
|
|
|
|
|
layer, err = datastore.FindLayer("layer-0", true, false)
|
|
|
|
if assert.Nil(t, err) && assert.NotNil(t, layer) {
|
|
|
|
assert.Len(t, layer.Features, 0)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Layer-1: one parent, adds two features, one vulnerability
|
|
|
|
layer, err = datastore.FindLayer("layer-1", false, false)
|
|
|
|
if assert.Nil(t, err) && assert.NotNil(t, layer) {
|
|
|
|
assert.Equal(t, layer.Name, "layer-1")
|
|
|
|
assert.Equal(t, "debian:7", layer.Namespace.Name)
|
|
|
|
if assert.NotNil(t, layer.Parent) {
|
|
|
|
assert.Equal(t, "layer-0", layer.Parent.Name)
|
|
|
|
}
|
|
|
|
assert.Equal(t, 1, layer.EngineVersion)
|
|
|
|
assert.Len(t, layer.Features, 0)
|
|
|
|
}
|
|
|
|
|
|
|
|
layer, err = datastore.FindLayer("layer-1", true, false)
|
|
|
|
if assert.Nil(t, err) && assert.NotNil(t, layer) && assert.Len(t, layer.Features, 2) {
|
|
|
|
for _, featureVersion := range layer.Features {
|
|
|
|
assert.Equal(t, "debian:7", featureVersion.Feature.Namespace.Name)
|
|
|
|
|
|
|
|
switch featureVersion.Feature.Name {
|
|
|
|
case "wechat":
|
2016-12-28 01:45:11 +00:00
|
|
|
assert.Equal(t, "0.5", featureVersion.Version)
|
2015-12-28 20:03:29 +00:00
|
|
|
case "openssl":
|
2016-12-28 01:45:11 +00:00
|
|
|
assert.Equal(t, "1.0", featureVersion.Version)
|
2015-12-28 20:03:29 +00:00
|
|
|
default:
|
|
|
|
t.Errorf("unexpected package %s for layer-1", featureVersion.Feature.Name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
layer, err = datastore.FindLayer("layer-1", true, true)
|
|
|
|
if assert.Nil(t, err) && assert.NotNil(t, layer) && assert.Len(t, layer.Features, 2) {
|
|
|
|
for _, featureVersion := range layer.Features {
|
|
|
|
assert.Equal(t, "debian:7", featureVersion.Feature.Namespace.Name)
|
|
|
|
|
|
|
|
switch featureVersion.Feature.Name {
|
|
|
|
case "wechat":
|
2016-12-28 01:45:11 +00:00
|
|
|
assert.Equal(t, "0.5", featureVersion.Version)
|
2015-12-28 20:03:29 +00:00
|
|
|
case "openssl":
|
2016-12-28 01:45:11 +00:00
|
|
|
assert.Equal(t, "1.0", featureVersion.Version)
|
2015-12-28 20:03:29 +00:00
|
|
|
|
|
|
|
if assert.Len(t, featureVersion.AffectedBy, 1) {
|
|
|
|
assert.Equal(t, "debian:7", featureVersion.AffectedBy[0].Namespace.Name)
|
|
|
|
assert.Equal(t, "CVE-OPENSSL-1-DEB7", featureVersion.AffectedBy[0].Name)
|
|
|
|
assert.Equal(t, types.High, featureVersion.AffectedBy[0].Severity)
|
|
|
|
assert.Equal(t, "A vulnerability affecting OpenSSL < 2.0 on Debian 7.0", featureVersion.AffectedBy[0].Description)
|
|
|
|
assert.Equal(t, "http://google.com/#q=CVE-OPENSSL-1-DEB7", featureVersion.AffectedBy[0].Link)
|
2016-12-28 01:45:11 +00:00
|
|
|
assert.Equal(t, "2.0", featureVersion.AffectedBy[0].FixedBy)
|
2015-12-28 20:03:29 +00:00
|
|
|
}
|
|
|
|
default:
|
|
|
|
t.Errorf("unexpected package %s for layer-1", featureVersion.Feature.Name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestInsertLayer(t *testing.T) {
|
2016-05-02 22:33:03 +00:00
|
|
|
datastore, err := openDatabaseForTest("InsertLayer", false)
|
2015-12-28 20:03:29 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Error(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer datastore.Close()
|
|
|
|
|
|
|
|
// Insert invalid layer.
|
|
|
|
testInsertLayerInvalid(t, datastore)
|
|
|
|
|
|
|
|
// Insert a layer tree.
|
|
|
|
testInsertLayerTree(t, datastore)
|
|
|
|
|
|
|
|
// Update layer.
|
2016-01-20 21:04:14 +00:00
|
|
|
testInsertLayerUpdate(t, datastore)
|
2015-12-28 20:03:29 +00:00
|
|
|
|
|
|
|
// Delete layer.
|
2016-01-20 21:04:14 +00:00
|
|
|
testInsertLayerDelete(t, datastore)
|
2015-12-28 20:03:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func testInsertLayerInvalid(t *testing.T, datastore database.Datastore) {
|
|
|
|
invalidLayers := []database.Layer{
|
2016-02-25 00:29:36 +00:00
|
|
|
{},
|
|
|
|
{Name: "layer0", Parent: &database.Layer{}},
|
|
|
|
{Name: "layer0", Parent: &database.Layer{Name: "UnknownLayer"}},
|
2015-12-28 20:03:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, invalidLayer := range invalidLayers {
|
|
|
|
err := datastore.InsertLayer(invalidLayer)
|
|
|
|
assert.Error(t, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func testInsertLayerTree(t *testing.T, datastore database.Datastore) {
|
|
|
|
f1 := database.FeatureVersion{
|
|
|
|
Feature: database.Feature{
|
2016-12-28 01:45:11 +00:00
|
|
|
Namespace: database.Namespace{
|
|
|
|
Name: "TestInsertLayerNamespace2",
|
2017-01-03 21:00:20 +00:00
|
|
|
VersionFormat: dpkg.ParserName,
|
2016-12-28 01:45:11 +00:00
|
|
|
},
|
|
|
|
Name: "TestInsertLayerFeature1",
|
2015-12-28 20:03:29 +00:00
|
|
|
},
|
2016-12-28 01:45:11 +00:00
|
|
|
Version: "1.0",
|
2015-12-28 20:03:29 +00:00
|
|
|
}
|
|
|
|
f2 := database.FeatureVersion{
|
|
|
|
Feature: database.Feature{
|
2016-12-28 01:45:11 +00:00
|
|
|
Namespace: database.Namespace{
|
|
|
|
Name: "TestInsertLayerNamespace2",
|
2017-01-03 21:00:20 +00:00
|
|
|
VersionFormat: dpkg.ParserName,
|
2016-12-28 01:45:11 +00:00
|
|
|
},
|
|
|
|
Name: "TestInsertLayerFeature2",
|
2015-12-28 20:03:29 +00:00
|
|
|
},
|
2016-12-28 01:45:11 +00:00
|
|
|
Version: "0.34",
|
2015-12-28 20:03:29 +00:00
|
|
|
}
|
|
|
|
f3 := database.FeatureVersion{
|
|
|
|
Feature: database.Feature{
|
2016-12-28 01:45:11 +00:00
|
|
|
Namespace: database.Namespace{
|
|
|
|
Name: "TestInsertLayerNamespace2",
|
2017-01-03 21:00:20 +00:00
|
|
|
VersionFormat: dpkg.ParserName,
|
2016-12-28 01:45:11 +00:00
|
|
|
},
|
|
|
|
Name: "TestInsertLayerFeature3",
|
2015-12-28 20:03:29 +00:00
|
|
|
},
|
2016-12-28 01:45:11 +00:00
|
|
|
Version: "0.56",
|
2015-12-28 20:03:29 +00:00
|
|
|
}
|
|
|
|
f4 := database.FeatureVersion{
|
|
|
|
Feature: database.Feature{
|
2016-12-28 01:45:11 +00:00
|
|
|
Namespace: database.Namespace{
|
|
|
|
Name: "TestInsertLayerNamespace3",
|
2017-01-03 21:00:20 +00:00
|
|
|
VersionFormat: dpkg.ParserName,
|
2016-12-28 01:45:11 +00:00
|
|
|
},
|
|
|
|
Name: "TestInsertLayerFeature2",
|
2015-12-28 20:03:29 +00:00
|
|
|
},
|
2016-12-28 01:45:11 +00:00
|
|
|
Version: "0.34",
|
2015-12-28 20:03:29 +00:00
|
|
|
}
|
|
|
|
f5 := database.FeatureVersion{
|
|
|
|
Feature: database.Feature{
|
2016-12-28 01:45:11 +00:00
|
|
|
Namespace: database.Namespace{
|
|
|
|
Name: "TestInsertLayerNamespace3",
|
2017-01-03 21:00:20 +00:00
|
|
|
VersionFormat: dpkg.ParserName,
|
2016-12-28 01:45:11 +00:00
|
|
|
},
|
|
|
|
Name: "TestInsertLayerFeature3",
|
2015-12-28 20:03:29 +00:00
|
|
|
},
|
2016-12-28 01:45:11 +00:00
|
|
|
Version: "0.56",
|
2015-12-28 20:03:29 +00:00
|
|
|
}
|
|
|
|
f6 := database.FeatureVersion{
|
|
|
|
Feature: database.Feature{
|
2016-12-28 01:45:11 +00:00
|
|
|
Namespace: database.Namespace{
|
|
|
|
Name: "TestInsertLayerNamespace3",
|
2017-01-03 21:00:20 +00:00
|
|
|
VersionFormat: dpkg.ParserName,
|
2016-12-28 01:45:11 +00:00
|
|
|
},
|
|
|
|
Name: "TestInsertLayerFeature4",
|
2015-12-28 20:03:29 +00:00
|
|
|
},
|
2016-12-28 01:45:11 +00:00
|
|
|
Version: "0.666",
|
2015-12-28 20:03:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
layers := []database.Layer{
|
2016-02-25 00:29:36 +00:00
|
|
|
{
|
2015-12-28 20:03:29 +00:00
|
|
|
Name: "TestInsertLayer1",
|
|
|
|
},
|
2016-02-25 00:29:36 +00:00
|
|
|
{
|
2016-12-28 01:45:11 +00:00
|
|
|
Name: "TestInsertLayer2",
|
|
|
|
Parent: &database.Layer{Name: "TestInsertLayer1"},
|
|
|
|
Namespace: &database.Namespace{
|
|
|
|
Name: "TestInsertLayerNamespace1",
|
2017-01-03 21:00:20 +00:00
|
|
|
VersionFormat: dpkg.ParserName,
|
2016-12-28 01:45:11 +00:00
|
|
|
},
|
2015-12-28 20:03:29 +00:00
|
|
|
},
|
|
|
|
// This layer changes the namespace and adds Features.
|
2016-02-25 00:29:36 +00:00
|
|
|
{
|
2016-12-28 01:45:11 +00:00
|
|
|
Name: "TestInsertLayer3",
|
|
|
|
Parent: &database.Layer{Name: "TestInsertLayer2"},
|
|
|
|
Namespace: &database.Namespace{
|
|
|
|
Name: "TestInsertLayerNamespace2",
|
2017-01-03 21:00:20 +00:00
|
|
|
VersionFormat: dpkg.ParserName,
|
2016-12-28 01:45:11 +00:00
|
|
|
},
|
|
|
|
Features: []database.FeatureVersion{f1, f2, f3},
|
2015-12-28 20:03:29 +00:00
|
|
|
},
|
2016-01-08 15:27:30 +00:00
|
|
|
// This layer covers the case where the last layer doesn't provide any new Feature.
|
2016-02-25 00:29:36 +00:00
|
|
|
{
|
2016-01-08 15:27:30 +00:00
|
|
|
Name: "TestInsertLayer4a",
|
|
|
|
Parent: &database.Layer{Name: "TestInsertLayer3"},
|
|
|
|
Features: []database.FeatureVersion{f1, f2, f3},
|
2015-12-28 20:03:29 +00:00
|
|
|
},
|
|
|
|
// This layer covers the case where the last layer provides Features.
|
|
|
|
// It also modifies the Namespace ("upgrade") but keeps some Features not upgraded, their
|
|
|
|
// Namespaces should then remain unchanged.
|
2016-02-25 00:29:36 +00:00
|
|
|
{
|
2016-12-28 01:45:11 +00:00
|
|
|
Name: "TestInsertLayer4b",
|
|
|
|
Parent: &database.Layer{Name: "TestInsertLayer3"},
|
|
|
|
Namespace: &database.Namespace{
|
|
|
|
Name: "TestInsertLayerNamespace3",
|
2017-01-03 21:00:20 +00:00
|
|
|
VersionFormat: dpkg.ParserName,
|
2016-12-28 01:45:11 +00:00
|
|
|
},
|
2015-12-28 20:03:29 +00:00
|
|
|
Features: []database.FeatureVersion{
|
|
|
|
// Deletes TestInsertLayerFeature1.
|
|
|
|
// Keep TestInsertLayerFeature2 (old Namespace should be kept):
|
|
|
|
f4,
|
|
|
|
// Upgrades TestInsertLayerFeature3 (with new Namespace):
|
|
|
|
f5,
|
|
|
|
// Adds TestInsertLayerFeature4:
|
|
|
|
f6,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
var err error
|
|
|
|
retrievedLayers := make(map[string]database.Layer)
|
|
|
|
for _, layer := range layers {
|
|
|
|
if layer.Parent != nil {
|
|
|
|
// Retrieve from database its parent and assign.
|
|
|
|
parent := retrievedLayers[layer.Parent.Name]
|
|
|
|
layer.Parent = &parent
|
|
|
|
}
|
|
|
|
|
|
|
|
err = datastore.InsertLayer(layer)
|
|
|
|
assert.Nil(t, err)
|
|
|
|
|
|
|
|
retrievedLayers[layer.Name], err = datastore.FindLayer(layer.Name, true, false)
|
|
|
|
assert.Nil(t, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
l4a := retrievedLayers["TestInsertLayer4a"]
|
2016-01-08 15:27:30 +00:00
|
|
|
if assert.NotNil(t, l4a.Namespace) {
|
|
|
|
assert.Equal(t, "TestInsertLayerNamespace2", l4a.Namespace.Name)
|
|
|
|
}
|
2015-12-28 20:03:29 +00:00
|
|
|
assert.Len(t, l4a.Features, 3)
|
|
|
|
for _, featureVersion := range l4a.Features {
|
|
|
|
if cmpFV(featureVersion, f1) && cmpFV(featureVersion, f2) && cmpFV(featureVersion, f3) {
|
|
|
|
assert.Error(t, fmt.Errorf("TestInsertLayer4a contains an unexpected package: %#v. Should contain %#v and %#v and %#v.", featureVersion, f1, f2, f3))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
l4b := retrievedLayers["TestInsertLayer4b"]
|
2016-01-08 15:27:30 +00:00
|
|
|
if assert.NotNil(t, l4b.Namespace) {
|
|
|
|
assert.Equal(t, "TestInsertLayerNamespace3", l4b.Namespace.Name)
|
|
|
|
}
|
|
|
|
assert.Len(t, l4b.Features, 3)
|
|
|
|
for _, featureVersion := range l4b.Features {
|
2015-12-28 20:03:29 +00:00
|
|
|
if cmpFV(featureVersion, f2) && cmpFV(featureVersion, f5) && cmpFV(featureVersion, f6) {
|
|
|
|
assert.Error(t, fmt.Errorf("TestInsertLayer4a contains an unexpected package: %#v. Should contain %#v and %#v and %#v.", featureVersion, f2, f4, f6))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-01-20 21:04:14 +00:00
|
|
|
func testInsertLayerUpdate(t *testing.T, datastore database.Datastore) {
|
|
|
|
f7 := database.FeatureVersion{
|
|
|
|
Feature: database.Feature{
|
2016-12-28 01:45:11 +00:00
|
|
|
Namespace: database.Namespace{
|
|
|
|
Name: "TestInsertLayerNamespace3",
|
2017-01-03 21:00:20 +00:00
|
|
|
VersionFormat: dpkg.ParserName,
|
2016-12-28 01:45:11 +00:00
|
|
|
},
|
|
|
|
Name: "TestInsertLayerFeature7",
|
2016-01-20 21:04:14 +00:00
|
|
|
},
|
2016-12-28 01:45:11 +00:00
|
|
|
Version: "0.01",
|
2016-01-20 21:04:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
l3, _ := datastore.FindLayer("TestInsertLayer3", true, false)
|
|
|
|
l3u := database.Layer{
|
2016-12-28 01:45:11 +00:00
|
|
|
Name: l3.Name,
|
|
|
|
Parent: l3.Parent,
|
|
|
|
Namespace: &database.Namespace{
|
|
|
|
Name: "TestInsertLayerNamespaceUpdated1",
|
2017-01-03 21:00:20 +00:00
|
|
|
VersionFormat: dpkg.ParserName,
|
2016-12-28 01:45:11 +00:00
|
|
|
},
|
|
|
|
Features: []database.FeatureVersion{f7},
|
2016-01-20 21:04:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
l4u := database.Layer{
|
|
|
|
Name: "TestInsertLayer4",
|
|
|
|
Parent: &database.Layer{Name: "TestInsertLayer3"},
|
|
|
|
Features: []database.FeatureVersion{f7},
|
|
|
|
EngineVersion: 2,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Try to re-insert without increasing the EngineVersion.
|
|
|
|
err := datastore.InsertLayer(l3u)
|
|
|
|
assert.Nil(t, err)
|
|
|
|
|
|
|
|
l3uf, err := datastore.FindLayer(l3u.Name, true, false)
|
|
|
|
if assert.Nil(t, err) {
|
|
|
|
assert.Equal(t, l3.Namespace.Name, l3uf.Namespace.Name)
|
|
|
|
assert.Equal(t, l3.EngineVersion, l3uf.EngineVersion)
|
|
|
|
assert.Len(t, l3uf.Features, len(l3.Features))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update layer l3.
|
|
|
|
// Verify that the Namespace, EngineVersion and FeatureVersions got updated.
|
|
|
|
l3u.EngineVersion = 2
|
|
|
|
err = datastore.InsertLayer(l3u)
|
|
|
|
assert.Nil(t, err)
|
|
|
|
|
|
|
|
l3uf, err = datastore.FindLayer(l3u.Name, true, false)
|
|
|
|
if assert.Nil(t, err) {
|
|
|
|
assert.Equal(t, l3u.Namespace.Name, l3uf.Namespace.Name)
|
|
|
|
assert.Equal(t, l3u.EngineVersion, l3uf.EngineVersion)
|
|
|
|
if assert.Len(t, l3uf.Features, 1) {
|
|
|
|
assert.True(t, cmpFV(l3uf.Features[0], f7), "Updated layer should have %#v but actually have %#v", f7, l3uf.Features[0])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update layer l4.
|
|
|
|
// Verify that the Namespace got updated from its new Parent's, and also verify the
|
|
|
|
// EnginVersion and FeatureVersions.
|
|
|
|
l4u.Parent = &l3uf
|
|
|
|
err = datastore.InsertLayer(l4u)
|
|
|
|
assert.Nil(t, err)
|
|
|
|
|
|
|
|
l4uf, err := datastore.FindLayer(l3u.Name, true, false)
|
|
|
|
if assert.Nil(t, err) {
|
|
|
|
assert.Equal(t, l3u.Namespace.Name, l4uf.Namespace.Name)
|
|
|
|
assert.Equal(t, l4u.EngineVersion, l4uf.EngineVersion)
|
|
|
|
if assert.Len(t, l4uf.Features, 1) {
|
|
|
|
assert.True(t, cmpFV(l3uf.Features[0], f7), "Updated layer should have %#v but actually have %#v", f7, l4uf.Features[0])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func testInsertLayerDelete(t *testing.T, datastore database.Datastore) {
|
|
|
|
err := datastore.DeleteLayer("TestInsertLayerX")
|
|
|
|
assert.Equal(t, cerrors.ErrNotFound, err)
|
|
|
|
|
|
|
|
err = datastore.DeleteLayer("TestInsertLayer3")
|
|
|
|
assert.Nil(t, err)
|
|
|
|
|
|
|
|
_, err = datastore.FindLayer("TestInsertLayer3", false, false)
|
|
|
|
assert.Equal(t, cerrors.ErrNotFound, err)
|
|
|
|
|
|
|
|
_, err = datastore.FindLayer("TestInsertLayer4a", false, false)
|
|
|
|
assert.Equal(t, cerrors.ErrNotFound, err)
|
|
|
|
|
|
|
|
_, err = datastore.FindLayer("TestInsertLayer4b", true, false)
|
|
|
|
assert.Equal(t, cerrors.ErrNotFound, err)
|
|
|
|
}
|
|
|
|
|
2015-12-28 20:03:29 +00:00
|
|
|
func cmpFV(a, b database.FeatureVersion) bool {
|
|
|
|
return a.Feature.Name == b.Feature.Name &&
|
|
|
|
a.Feature.Namespace.Name == b.Feature.Namespace.Name &&
|
2016-12-28 01:45:11 +00:00
|
|
|
a.Version == b.Version
|
2015-12-28 20:03:29 +00:00
|
|
|
}
|